/* Hereburgh Ticksheet
   -------------------
   Events run down the page, members across it. There are fewer events than
   members, so the long axis is the scroll axis rather than the pan axis - and
   with your own column pinned beside the event, your answer is always on
   screen without moving anything. */

/* The side's own colours, taken from the badge: a navy ring, gold windmill
   sails and red fish on white. --green is kept as an alias so nothing that
   still refers to it breaks; everything reads navy now. */
:root {
    --bg:            #f4f5f9;
    --paper:         #ffffff;
    --ink:           #17203a;
    --ink-soft:      #5a6480;
    /* THE ONLY TWO LINE COLOURS. There is no --rule, and there was no --rule
       when seventeen declarations were written against one: an undefined
       custom property makes the whole declaration invalid, and the browser
       drops it without a word - so every card border and every separator on
       the meetings, docs and changelog pages was simply absent, in the source
       and correct in every grep. Same shape as the `rgb(var(--x) / a)` trap
       below: present in the file, ignored by the browser. */
    --line:          #d3d8e6;
    --line-strong:   #a3abc4;

    --navy:          #0018a8;   /* the badge ring and lettering */
    --navy-deep:     #001272;
    --navy-soft:     #e7eafa;
    --navy-mid:      #c6cef0;
    --gold:          #f0d848;   /* the sails */
    --gold-deep:     #8a6a10;   /* the same gold, dark enough to read on white */
    --red:           #cc2a22;   /* the fish */
    --red-deep:      #a81f18;

    /* Older names, kept pointing at the new palette. */
    --green:         var(--navy);
    --green-soft:    var(--navy-soft);
    --green-mid:     var(--navy-mid);

    /* Yes, no and possible, at deliberately unequal strength: the grid is read
       to find out who is coming, so yes is the one worth seeing first.
       Muting the red on a no put it in the same brown as the gold on a
       possible, and the two became hard to tell apart, so no goes cool instead
       of dark-warm. Three hues now, one per temperature - saturated blue,
       cool grey, warm amber - which stay distinct at a glance and survive
       red-green colour blindness, the common kind. */
    --yes:           #0018a8;
    --no:            #79839c;
    --maybe:         #9c6a00;

    /* Confirmed is a status, not a brand colour, so it is allowed a green the
       badge does not have - the one colour everybody already reads as "yes,
       this is happening". */
    --confirmed:     #1c7a44;

    /* Alternating rows. Every column carries the stripe, including the three
       pinned ones - a band that stops at the event column is a band you cannot
       follow back to the name of the event, which is the one thing it exists
       to do. Each column keeps its own hue and steps by roughly the same
       amount, so the stripe reads as one band crossing the whole row rather
       than four unrelated shades. */
    --row-bg:        var(--paper);
    --row-alt:       #eef1fa;
    --ev-bg:         #f7f9fd;
    --ev-alt:        #e8ecf7;
    --you-bg:        var(--navy-soft);
    --you-alt:       #d9def4;
    --tot-bg:        #eef1f8;
    --tot-alt:       #e0e5f3;

    /* The two edit tints. Deliberately different hues: "somebody changed my
       answer" is a more important signal than "I changed my answer".
       Space-separated, because they are used as rgb(var(--x) / alpha) below -
       commas there produce rgb(1, 2, 3 / 0.3), which is not valid CSS and is
       dropped silently, leaving the tint invisible. */
    --edit-self:     108 132 216;  /* a lighter blue than the brand navy */
    --edit-other:    245 205 40;   /* yellow, not the browner gold it was */
    /* The corner marker is drawn at full strength on white, where the wash's
       yellow all but disappears. It gets a darker one of the same hue. */
    --edit-other-ink: 168 128 0;
    --edit-alpha:    0.34;

    /* What you have not answered.
       Its own colour, and a red one: it is the only thing on the sheet asking
       the reader to do something, and it was competing with the gold on a
       "possible" - which is an answer, and so the opposite of this. */
    --needs:         208 42 34;

    /* Layout zoom. Driving font size and track widths rather than
       transform: scale() keeps position:sticky offsets honest and tap targets
       aligned with what you can actually see.

       0.9 is zoom.js's own default - BASE 0.9 at step 100% - and has to stay
       equal to it. It was 1, which is what the dances grid painted its first
       frame at before snapping down to 0.9: 111%, visible because that page
       is big enough to be painted before a deferred script has run. zoom.js
       now sets the real value in the head, so this is what a page renders at
       with no JavaScript at all. */
    --grid-scale:    0.9;
    --ev-w:          calc(10.5rem * var(--grid-scale));
    --you-w:         calc(3.4rem * var(--grid-scale));
    --tot-w:         calc(5.5rem * var(--grid-scale));
    --mem-w:         calc(2.95rem * var(--grid-scale));
    --grid-font:     calc(0.85rem * var(--grid-scale));
    /* The size an answer is set at, and now the size of everything under it in
       the cell as well - the D, the M and the percentage. It is a variable and
       not three copies of the same calc() because the badges had drifted to
       7.92px against the answer's 14.69px, which is under half, and nothing in
       the stylesheet said the two were meant to be related. One value, used by
       the cell and by all three of its slots. */
    --answer-font:   calc(var(--grid-font) * 1.2);
    --head-h:        calc(7.5rem * var(--grid-scale));
}

* { box-sizing: border-box; }

body {
    margin: 0;
    background: var(--bg);
    color: var(--ink);
    font: 16px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    -webkit-text-size-adjust: 100%;
}

/* ---------- top bar ---------- */

.topbar {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.5rem 0.9rem;
    background: var(--green);
    color: #fff;
    position: sticky;
    top: 0;
    z-index: 60;
}

.topbar__brand {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    flex: none;
    color: #fff;
    font-weight: 650;
    text-decoration: none;
}

/* The badge is a circle on a white square, so a round crop shows just the
   badge - which reads correctly against the navy bar. The image itself has
   empty alt: the link carries the name, so nothing is lost when the wordmark
   is hidden on a narrow screen. */
.topbar__logo {
    display: block;
    width: 1.8rem;
    height: 1.8rem;
    border-radius: 50%;
    flex: none;
}
/* The two links about you, pushed to the far end together. The auto margin is
   on the group rather than on the name, so the guide still sits hard right
   when nobody is signed in and there is no name beside it. */
.topbar__end {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-left: auto;
    flex: none;
}

/* Never wrapped, never shrunk: two lines of it push down into the tabs, and
   whose sheet you are signed in as is not a thing to leave half-legible. */
.topbar__me    { color: #c3ccf2; text-decoration: none; font-size: 0.9rem;
                 white-space: nowrap; flex: none;
                 display: inline-flex; align-items: center; gap: 0.35rem; }
.topbar__me:hover { color: #fff; }

/* The cog says the name is a way in to your settings rather than a label
   saying who you are. Sized in em so it tracks the name beside it, and left at
   currentColor so the whole link lightens together on hover. */
.topbar__cog { width: 1.05em; height: 1.05em; flex: none; }

/* Quieter than a tab and quieter than your own name - it is there for the
   first week and then hardly ever. The "?" carries it on a narrow screen,
   where the word itself is dropped rather than squeezing the tabs. */
.topbar__help {
    color: #c3ccf2; text-decoration: none; font-size: 0.9rem;
    white-space: nowrap; flex: none;
    padding: 0.15rem 0.45rem; border-radius: 5px;
    border: 1px solid rgba(255,255,255,0.28);
}
.topbar__help-q    { font-weight: 700; }
.topbar__help-word { margin-left: 0.3rem; }
.topbar__help:hover     { color: #fff; border-color: rgba(255,255,255,0.6); }
.topbar__help.is-active { background: var(--bg); color: var(--ink); border-color: var(--bg); font-weight: 600; }

/* min-width: 0 is what lets the tab strip actually shrink and scroll. Without
   it a flex item refuses to go below the width of its contents, so the tabs
   push the member's name off the end of the bar instead of scrolling. */
.tabs { display: flex; gap: 0.15rem; overflow-x: auto; scrollbar-width: none; min-width: 0; }
.tabs::-webkit-scrollbar { display: none; }

.tab {
    color: #c3ccf2;
    text-decoration: none;
    padding: 0.3rem 0.65rem;
    border-radius: 5px 5px 0 0;
    font-size: 0.88rem;
    white-space: nowrap;
}
.tab:hover     { background: rgba(255,255,255,0.12); color: #fff; }
.tab.is-active { background: var(--bg); color: var(--ink); font-weight: 600; }

/* ---------- grid chrome ---------- */

/* THE BAR STAYS WHEN THE PAGE SCROLLS. On a dance list the page really does
   scroll - the sheet is capped at 62svh and the legend and the history sit
   under it - and scrolling down to read the foot was taking the zoom, the View
   button and Preview off the top of the screen. The two other grids fill the
   window and never scroll the page at all, so this shows there and costs
   nothing here; it also helps any grid page in a short window, where the page
   scrolls for the same reason.

   `top: var(--topbar-h)` and not `top: 0`: `.topbar` is sticky at 0 with
   z-index 60, so a gridbar stuck at 0 slides underneath it and disappears,
   which is worse than scrolling away. The value is measured by
   before-table.js in the first painted frame and kept up to date on resize -
   see the comment there for why it is not a number written here.

   The background is what makes it a bar rather than a window on the table
   passing behind it, and z-index 50 puts it over the grid's own sticky cells
   at 10 and under the topbar at 60. */
.gridbar {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.45rem 0.9rem;
    flex-wrap: wrap;
    position: sticky;
    top: var(--topbar-h, 0px);
    z-index: 50;
    background: var(--bg);
}

.zoom { display: inline-flex; gap: 0.2rem; }

.btn {
    font: inherit;
    font-size: 0.85rem;
    border: 1px solid var(--line-strong);
    background: var(--paper);
    color: var(--ink);
    border-radius: 5px;
    padding: 0.25rem 0.6rem;
    cursor: pointer;
}
.btn:hover  { border-color: var(--green); }
/* A link wearing .btn is a button, so it does not want a link's underline
   through the middle of it. Only ever an anchor: btn--link is the opposite
   thing - a button that wants to look like a link - and is only used on real
   <button> elements. */
a.btn { display: inline-block; text-decoration: none; }
.btn--icon  { min-width: 2.1rem; text-align: center; }
.btn--quiet { border-color: transparent; background: transparent; color: var(--ink-soft); }
.btn--quiet:hover { color: var(--green); }
.btn--link  { border: 0; background: none; color: var(--green); text-decoration: underline; padding: 0 0.2rem; cursor: pointer; }

.pill {
    font-size: 0.82rem; background: #fdf3d8; border: 1px solid #e0cd93;
    border-radius: 99px; padding: 0.15rem 0.7rem; color: #6a5320;
}

.empty { padding: 2rem 1rem; color: var(--ink-soft); }

/* ---------- the grid ---------- */

/* The scroller takes the height that is left over, rather than a height
   somebody guessed at. It was max-height: calc(100dvh - 7.5rem) - a constant
   standing in for the chrome above it - and 7.5rem was wrong on both of the
   pages it applied to. Measured at 390x844: the events grid has 90px above
   the scroller and the dances page 139px, because the note in its gridbar
   wraps to a second line. So the events grid left a strip of viewport unused
   and the dances grid ran 19px past the bottom of it, which put page
   scrolling on the one kind of page that is trying not to have any.

   A flex column from the body down asks the browser instead of telling it, so
   a third line in that note, the years strip on a past year, a longer footer
   or a phone nobody has seen all come out right without a number changing.

   min-height, not height, and the scroller keeps a floor: if the chrome ever
   does not fit - a short window, desktop zoom at 200% - the body grows past
   the viewport and the page scrolls, which is ugly and reachable. With height
   and overflow: hidden the grid would be squeezed towards nothing with no way
   to get at it, which is the stranding the viewport rules already fight.

   100vh first because it is understood everywhere: an old phone that has
   never heard of svh drops that line and keeps a viewport-height page, where
   dropping the only declaration would leave the scroller sitting at its 8rem
   floor.

   SVH AND NOT DVH, and this is the one that was wrong (9 September 2026).
   `dvh` is the DYNAMIC viewport, and on Chrome for Android it changes as you
   scroll: the URL bar hides on the way down, the viewport grows, and
   `100dvh` grows with it - so this whole flex chain, and the scroller hanging
   off it, re-lays-out in the middle of a scroll. The side reported the last
   event on the ticksheet not drawing properly on reaching the bottom of the
   page, on Chrome on Android, and a zoom - any forced relayout - putting it
   right. That is the shape of a layout that moved under the paint, it happens
   only at the bottom because that is when the toolbar hides, and it is
   intermittent because it depends on whether the toolbar had already gone.

   `svh` is the SMALL viewport - the one with the toolbar showing - and it
   never moves. The cost is real and is the right way round: with the toolbar
   hidden the page is sized a little short, leaving slack below the grid on a
   page that scrolls anyway. A stable height on a page whose whole point is
   frozen panes beats a height that tracks the chrome perfectly and moves
   while somebody is reading. The reasoning this replaces was that dvh
   "follows a phone's shrinking viewport", which it does - it also follows it
   growing, mid-gesture.

   NOT VERIFIABLE FROM HERE: headless Chrome has no URL bar, so svh, dvh and
   vh are all the same number and every measurement is identical either way.
   Proved only by a phone. */
body.is-grid {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    min-height: 100svh;
}

/* min-height: 0 on both, or a flex item refuses to shrink below the content
   inside it - and 56 dances is a lot of content - so the scroller would be
   pushed off the bottom of the screen again. */
body.is-grid > main {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

/* Only ever on a grid page, so it flexes unconditionally. The 4rem of bottom
   padding it used to carry was room to scroll past the table; there is no
   scrolling past it now. */
.gridwrap {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

/* A basis in a column IS a height - the trap this project has already fallen
   into once - and here that is exactly what is wanted: 8rem is the least the
   scroller will be squeezed to before the page starts scrolling instead. */
.gridscroll {
    flex: 1 1 8rem;
    overflow: auto;
    background: var(--paper);
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
    overscroll-behavior: contain;
}

.grid {
    /* separate, not collapse: collapsed borders detach from sticky cells and
       smear when the grid is scrolled */
    border-collapse: separate;
    border-spacing: 0;
    font-size: var(--grid-font);
    table-layout: fixed;

    /* Without this the table takes the width of its container and fixed layout
       squeezes every column to fit - so the columns end up narrower than the
       variables say, while the sticky left offsets below are still calculated
       from those variables. The pinned columns then land in the wrong place
       and sit on top of the first member's column. max-content makes the table
       as wide as the columns actually need and lets the scroller do the
       panning, which is the whole point of it. */
    width: max-content;
}

.grid th, .grid td {
    border-right: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
    background-color: var(--row-bg);
}

/* --- the header row, pinned to the top --- */

.grid .head {
    position: sticky;
    top: 0;
    z-index: 20;
    height: var(--head-h);
    vertical-align: bottom;
    padding: 0.3rem 0.15rem;
    background: #fafbfe;
    font-weight: 500;
}

/* Names read bottom-to-top so the columns can stay narrow enough to fit a
   useful number of people on a phone. */
.vert {
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    white-space: nowrap;
    display: block;
    margin: 0 auto;
    max-height: calc(var(--head-h) - 0.6rem);
    overflow: hidden;
}

.grid .head--mem { width: var(--mem-w); text-align: center; }
.head--mem.is-musician .vert { color: var(--green); font-weight: 650; }

/* --- the three pinned columns --- */

/* The left offsets are measured from the rendered columns by app.js and set as
   --pin-you and --pin-tot. Deriving them from the width variables instead only
   works while the browser lays the columns out at exactly those widths - and
   when it does not, the pinned columns land on top of the first member's
   column instead of beside it. The variables stay as the fallback for the
   moment before the script runs, and for no-JS. */
.head--event, .ev  { left: 0;                                                width: var(--ev-w); }
.head--you,   .cell--you { left: var(--pin-you, var(--ev-w));                width: var(--you-w); }
.head--tot,   .tot { left: var(--pin-tot, calc(var(--ev-w) + var(--you-w))); width: var(--tot-w); }

.grid .ev, .grid .tot { position: sticky; z-index: 10; }
/* .cell--you is pinned further down, after ".grid .cell" has had its say. */
.grid thead .head--event,
.grid thead .head--you,
.grid thead .head--tot { z-index: 30; }   /* corner: sticky on both axes */

.grid .head--event {
    text-align: left;
    padding: 0.35rem 0.5rem;
    vertical-align: bottom;
    color: var(--ink-soft);
}

/* The badge is a circle drawn on a white square, so a round crop loses the
   corners and nothing else. It scales with the grid zoom, and gives way
   entirely when the header is too short to hold it - on a phone the columns
   matter more than the decoration. */
.head__logo {
    display: block;
    width: calc(4.6rem * var(--grid-scale));
    height: auto;
    border-radius: 50%;
    margin: 0 auto 0.3rem;
}
/* Event on the left, Add hard against the right edge of the column. The
   corner cell is the one part of the grid that belongs to no event and to no
   member, which makes it the right place to say "there is another one". */
.head__label {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.4rem;
}
.head__add {
    font-size: 0.8em;
    font-weight: 650;
    text-decoration: none;
    white-space: nowrap;
    padding: 0.1em 0.55em;
    border-radius: 99px;
    color: var(--green);
    background: var(--green-soft);
    border: 1px solid var(--green-mid);
}
.head__add:hover { background: var(--green); border-color: var(--green); color: #fff; }

@media (max-width: 40rem) {
    .head__logo { width: calc(3.4rem * var(--grid-scale)); }
}
.grid .head--you { background-color: var(--green-soft); }
.grid .head--tot { background-color: #eef1f8; }

/* A firm edge where the pinned block ends and the panning part begins. */
.tot, .head--tot { border-right: 2px solid var(--line-strong); }

/* --- the event cell --- */

.grid .ev {
    text-align: left;
    font-weight: 400;
    padding: 0.55rem 0.55rem 0.6rem;
    background-color: var(--ev-bg);
    vertical-align: top;
    line-height: 1.3;
}

/* Title, date, venue and the rest are separate facts about the event, and set
   solid they read as one paragraph you have to unpick. One gap between every
   pair of lines, declared once here so nothing drifts as lines are added. */
.grid .ev > * + * { margin-top: 0.4em; }
.ev__title {
    display: block;
    font-size: 1.12em;
    font-weight: 650;
    line-height: 1.15;
    color: var(--ink);
    text-decoration: none;
}
.ev__title:hover { text-decoration: underline; text-decoration-color: var(--green); }
.ev__part  { display: block; font-size: 0.86em; color: var(--green); font-weight: 600; }

/* When it is matters as much as what it is - it is the thing people scan for
   when deciding whether they are free - so the date carries real weight rather
   than sitting in small grey type under the title. The time sits with it. */
/* The date, the time and the confirmed stamp share a line and wrap between
   themselves when the column is too narrow to hold them. */
.ev__when  { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.2em 0.45em; line-height: 1.3; }
.ev__date  { font-size: 1em; font-weight: 650; color: var(--ink); }
/* Its own line rather than trailing the date behind a separator: the column is
   narrow enough that the date often wraps, and a separator then strands itself
   at the start of the next line. */
.ev__time  { display: block; font-size: 0.95em; font-weight: 600; color: var(--green); white-space: nowrap; }
.ev__where { display: block; font-size: 0.82em; color: var(--ink-soft);
             overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ev__due   { display: block; font-size: 0.8em; color: var(--maybe); }
.tick      { color: var(--green); }

/* A stamp, not a tick. Whether the dance out is actually on is what people
   check before deciding whether they are free, and a small check mark beside
   the date was doing none of that work. */
.ev__conf {
    display: inline-flex;
    align-items: center;
    gap: 0.3em;
    background: var(--confirmed);
    color: #fff;
    font-size: 0.72em;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    padding: 0.2em 0.55em;
    border-radius: 3px;
    line-height: 1.5;
}
.ev__conf-tick { font-size: 1.15em; line-height: 1; }

/* --- the kind of dance out ---
   The tint goes on as a background-image over the striped background-color
   rather than replacing it, so the alternating band still runs through the
   event column instead of stopping wherever a type is set.
   Every background above is written as background-color for exactly this
   reason: the "background" shorthand resets background-image to none, and the
   striping rules are more specific than this one, so with the shorthand the
   wash was set here and wiped there without a word.
   --type-rgb is space-separated for the same reason the edit tints are: rgb()
   with a slashed alpha will not take commas, and drops the declaration in
   silence if it gets them. */
.ev--typed {
    background-image: linear-gradient(rgb(var(--type-rgb) / 0.22), rgb(var(--type-rgb) / 0.22));
    box-shadow: inset 5px 0 0 rgb(var(--type-rgb) / 0.9);
    padding-left: 0.7rem;
}
/* A row you still owe an answer to outranks its type on the left edge: what
   you owe is the thing you are scanning for. The type still has the wash.

   This painted the bar in --maybe gold, which contradicted the sentence above
   it - the comment said "what you owe", the colour said "possible" - and the
   untyped version four rules down has always used --needs. It was invisible
   because only a typed event shows it. Now that gold means "you said maybe",
   the same bar in gold on a row you have NOT answered is actively misleading,
   so it says --needs like its twin. */
.row--needs-you .ev--typed {
    box-shadow: inset 7px 0 0 rgb(var(--needs));
    padding-left: 0.85rem;
}

.ev__type {
    font-size: 0.72em;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: rgb(var(--type-rgb));
    white-space: nowrap;
}

/* "You are looking after this", above the title of a dance out that is yours.
   A quiet filled tag rather than a fourth thing on the left edge - that edge
   already carries the type wash and the needs-you and maybe bars, and a third
   claim on it would be a fight nobody wins. */
/* Built to the same recipe as .ev__conf above, deliberately: it was a soft
   tinted tag beside a solid filled stamp, so on a row carrying both the thing
   that is somebody's JOB read as quieter than the thing that is merely true of
   the booking. Same size, same weight, same tracking, same padding, same
   radius, same glyph-then-word shape - filled, in the brand navy, so the two
   read as one family of stamps rather than as a stamp and a hint. */
.ev__mine {
    display: inline-flex;
    align-items: center;
    gap: 0.3em;
    background: var(--green);
    color: #fff;
    font-size: 0.72em;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    padding: 0.2em 0.55em;
    border-radius: 3px;
    line-height: 1.5;
}

.ev__mine-mark { font-size: 1.05em; line-height: 1; }

/* The same label on the event page, where there is room for a filled tag. */
.typetag {
    display: inline-block;
    background: var(--type);
    color: #fff;
    font-size: 0.78em;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    padding: 0.15em 0.55em;
    border-radius: 3px;
    margin-right: 0.35em;
}

/* Rows still needing your answer carry a left edge, so what you owe is
   findable while scrolling without reading a thing. Wide enough to register
   from the far side of the grid, where it is the only part of the row you can
   still see. */
.row--needs-you .ev {
    box-shadow: inset 7px 0 0 rgb(var(--needs));
    padding-left: 0.85rem;   /* clear of the bar, so the title does not sit on it */
}

/* A maybe gets the same edge in the possible colour: it has been answered, so
   it is not red, but it is the one answer that has to be come back to - it
   cannot be counted when somebody is working out whether the side has a set.
   Border only, deliberately: the cell itself already carries --maybe as ink,
   and washing the row as well would say it twice.

   Narrower than the needs-you bar, because it asks less. The two are mutually
   exclusive - no row at all, or a row saying maybe - so there is no precedence
   to settle. */
.row--maybe-you .ev,
.row--maybe-you .ev--typed {
    box-shadow: inset 4px 0 0 var(--maybe);
    padding-left: 0.85rem;
}

.grid tbody tr.row--maybe-you .cell--you {
    box-shadow: inset 0 0 0 2px var(--maybe);
}

/* --- totals --- */

/* The size of one totals line, in one place. The narrow-screen media query
   moves this rather than re-declaring the font-size on `.tot span`, so the
   indented lines can stay a fixed FRACTION of it at every width. They could
   not before: both rules said 0.8em, which is smaller than the 0.88em normally
   on a line and exactly equal to the 0.8em a phone was already using - so on
   the screen most of the side is holding, the breakdown and the Yes came out
   identical. Measured 9.792px against 9.792px, which is how it was found. */
.grid .tot {
    text-align: left;
    padding: 0.3rem 0.4rem;
    background-color: var(--tot-bg);
    font-variant-numeric: tabular-nums;
    --tot-line: 0.88em;
}
/* Number first, then what it counts. The numbers still line up with each
   other: they sit in a right-aligned column of their own, because three counts
   read down a column get compared with each other and ragged digits make that
   harder than it needs to be. */
.tot span {
    display: flex;
    gap: 0.45em;
    font-size: var(--tot-line);
    line-height: 1.4;
    color: var(--ink-soft);
}
.tot b {
    font-weight: 650;
    font-variant-numeric: tabular-nums;
    min-width: 1.35em;
    text-align: right;
    flex: none;
}
.tot__y b { color: var(--yes); }
.tot__p b { color: var(--maybe); }
.tot__u b { color: var(--ink-soft); }
/* Dance, Music and Guest are a breakdown of the Yes above them, not three more
   totals - none of them adds up to it and none is meant to. Set smaller AND
   stepped in behind a hairline, because smaller alone was not enough: five
   numbers down a narrow column read as one block with nothing saying which
   belong to which. The bracket says it at a glance.

   Everything in em, so it follows the grid's own zoom without a calc().

   `.tot span.tot__sub` and not `.tot__sub` alone: `.tot span` above is a class
   AND an element, so it beats a lone class and the font-size here did nothing
   whatever - the three lines rendered at exactly the size of the Yes, measured
   at 9.792px against 9.792px. Third time today, after `.guide__part` losing to
   `.panel h2`. When a size does not take, read what else in this file already
   selects the element. */
.tot span.tot__sub {
    /* Three quarters of whatever a full line is, at every width. A ratio and
       not a size of its own: 0.92 was too gentle to see, and the flat 0.8 that
       replaced it happened to equal the line size on a phone, so it vanished
       again on the screens that matter most. */
    font-size: calc(var(--tot-line) * 0.75);
    margin-left: 0.2em;
    padding-left: 0.5em;
    border-left: 1px solid var(--line);
}
.tot__sub b { color: var(--ink-soft); }

/* `.tot span { display: flex }` is an author rule and beats the browser's own
   [hidden], so a hidden Guest would sit there reading "0 Guest". */
.tot span[hidden] { display: none; }

/* --- answer cells --- */

.grid .cell {
    width: var(--mem-w);
    text-align: center;
    cursor: pointer;
    position: relative;
    /* Middle, and the levelling is done by .cell__stack below rather than by
       aligning to the top. Top-aligning made the answers level and put them
       hard against the top of the row, which on a row 108px tall left the
       answer floating above a field of nothing. */
    vertical-align: middle;
    /* The answers are the content of the grid; everything else is a label for
       them. They were set at the same size as the venue line, which is the
       wrong way round. Named, rather than the 1.2em it was, so the three slots
       under the answer can be set to the same thing and say so. */
    font-size: var(--answer-font);
    font-weight: 600;
    padding: 0.35rem 0.1rem;
    user-select: none;
}
.cell:focus-visible { outline: 2px solid var(--green); outline-offset: -2px; }
.is-readonly .cell  { cursor: default; }

/* THE THREE ANSWERS ARE ONE SIZE AND ONE WEIGHT, AND COLOUR IS WHAT SEPARATES
   THEM. These three rules carried a font-size and a font-weight each - Y at
   1.2em and 800, Pos at 0.72em and 700 - for the whole life of the grid, and
   not one of the six declarations ever applied: `.grid .cell` above sets both
   properties at two classes to their one, so it wins on specificity whatever
   the order. Measured 9 September 2026 - every answer in a row reports
   14.688px and weight 600, which is `.grid .cell`'s value and not any of
   theirs. The dead declarations are gone rather than promoted, because what
   the side has been reading for a fortnight is one size, and making Y suddenly
   heavier than N is a change to the sheet rather than a repair to the
   stylesheet. If unequal weight is wanted, it is a decision to take again and
   it has to be written as `.grid .cell--yes` to have any effect at all.

   The opacity that used to dim a no is gone - colour does that job, and
   stacking opacity on top made it faint rather than quiet. */
.cell--yes   { color: var(--yes); }
.cell--no    { color: var(--no); }
.cell--maybe { color: var(--maybe); letter-spacing: 0.01em; }

.cell__v i { font-style: normal; font-size: 0.8em; opacity: 0.8; }

/* Your own column: the point of the whole layout, so it reads as yours. */
/* Must come after ".grid .cell", which sets position: relative at the same
   specificity and would otherwise win by being later. A relative cell treats
   "left" as an offset from where it already sits, so instead of pinning, your
   column shifts right by the width of the event column and lands on top of the
   first member's. */
/* The font-size that used to be here was 1.05em, and it was making your own
   answer SMALLER than everybody else's - the opposite of what this rule is
   for. It was right when it was written: `.cell` was then set at the size of
   the venue line and 1.05em lifted your column above it. `.cell` went to
   1.2em when the answers became the content of the grid, and this was left
   behind - and it beats `.grid .cell` by being later at the same specificity,
   so your column was the one column reading 12.85px in a row of 14.69px.
   Measured 9 September 2026. Your column reads as yours through the green and
   the extra width; the answer in it is the same size as the answer beside it,
   which is what lets a row be read across. */
.grid .cell--you {
    position: sticky;
    z-index: 10;
    background-color: var(--you-bg);
    width: var(--you-w);
}
.cell--you:hover { background-color: var(--green-mid); }

/* --- the recency fade ---
   --tint runs 1 (just edited) to 0 (fadeDays old), set per cell by the
   server. Squaring it eases the curve: clearly visible for the first few days,
   the window when it matters, then drifting away quietly. */
.cell--edited-self::before,
.cell--edited-other::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    /* Worked out per cell by the template. A calc() here on a custom property
       is dropped in silence when it fails to resolve, which left every edit at
       full strength and no fade at all. */
    opacity: var(--tint, 1);
    /* Only ever seen on a change arriving from somebody else, which the script
       paints from 0 across one frame. A tint set at page load does not
       transition - there is no previous value to come from - and your own tap
       is painted at full strength on purpose: you did it, you expect it now.
       So this is the practice-night rule, and nothing else: twenty answers
       going in over a minute settle rather than blink. */
    transition: opacity 200ms ease-out;
}
/* One strength for both, set on the Look page. */
.cell--edited-self::before  { background: rgb(var(--edit-self) / var(--edit-alpha)); }
.cell--edited-other::before { background: rgb(var(--edit-other) / var(--edit-alpha)); }

/* THERE IS NO CORNER MARKER ANY MORE, and the reasoning that put one there is
   worth keeping because it was not wrong - it was "never colour alone", a
   second encoding of the same signal for a colour-blind member. What it missed
   is that the wash is not colour alone: it is a LIGHTNESS change over the
   cell's own background, so whether a cell has been edited recently reads
   without any hue at all, and the marker never distinguished your own edit
   from somebody else's - both triangles were the same shape and differed only
   in colour, which is the one thing it was there to avoid relying on. What it
   cost was a small solid wedge on every recently-touched cell across a sheet
   twenty-two columns wide, and the side asked for it to go.
   The title attribute is what now says in words what the colour means, and it
   was always there. See templates/grid.php.
   It survives in ONE place, below, under forced-colours: the wash is
   deliberately dropped there because the platform paints its own backgrounds,
   so the marker is the only signal left and is drawn in full. */

/* THE WHOLE STACK IS ONE BOX OF FIXED HEIGHT, and that is what levels the row.
   Every cell's content is then the same height whatever it holds, so the cell
   can go on centring it and the answer lands in the same place in all of them
   - the place a three-line cell's answer occupies. A one-line cell reserves
   the two lines it is not using.
   That is the difference between this and simply aligning to the top, which
   also made them level but put every answer hard against the top of the row.
   Height and not min-height, because three lines is the most a cell can hold
   and a fourth would mean something else had gone wrong.

   ADDED UP RATHER THAN MEASURED AND ROUNDED, which it was: a flat 3rem covered
   the old stack with a hair to spare and had to be re-measured the moment the
   percentage and the badges grew to the answer's size. It is now the parts -
   the answer's band, the two margins, and the two slots under it, each of
   which is the answer's own size - so the box follows the type instead of
   being a number somebody has to remember to change. */
.cell__stack {
    display: block;
    height: calc(1.74rem * var(--grid-scale) + var(--answer-font) * 2 + 2px);
}

/* With the badges off there are at most two lines - the answer and, on a
   maybe, how likely - so the box shrinks to match and the grid tightens up.
   The class is on <html> and set by zoom.js before the first paint, because
   this changes the height of every row and a late switch would be a visible
   jolt on a page that is mostly table. */
:root.no-dm-badges .cell__stack {
    height: calc(1.52rem * var(--grid-scale) + var(--answer-font));
}
:root.no-dm-badges .cell__as    { display: none; }

/* The same switch, on a dance out's own page: the D and M beside the names in
   the Yes list are the same fact and go with them. Their own class rather than
   .badge--dan, which would take the members list with it - those describe the
   MEMBER and are nothing to do with one dance out's answers. */
:root.no-dm-badges .badge--cap { display: none; }

/* The button that does it wears the badges it controls, so the same class
   greys them out. State in CSS and not in the script: it is then right in the
   first painted frame, like the grid it describes. */
.btn--badges {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    padding-left: 0.4rem;
    padding-right: 0.4rem;
    /* The containing block for the strike below. Nothing else is needed:
       the strike is a background on a pseudo-element inset to 0, so it is
       already inside the button and already clipped by its own inherited
       border-radius. An overflow:hidden went in here on the strength of a
       screenshot that looked like it was leaking past the corners, and was
       taken out again when the box was measured - 60x30 inside a 62x32
       button, corner to corner exactly as intended. */
    position: relative;
}

/* Tighter than on a member page, where they trail a name: here they are the
   whole of the button and the default min-width leaves a gap down the middle. */
.btn--badges .badge {
    min-width: 1.35rem;
    cursor: inherit;
}

:root.no-dm-badges .btn--badges .badge {
    background: #eef0f5;
    color: var(--ink-soft);
    border-color: var(--line);
}

/* Struck through when they are off. Greying alone says "quiet", which is not
   the same as "not there" - the stroke is the mark everything else uses for
   off, and it is legible at a glance where a shade of grey has to be compared
   with something.

   A gradient rather than a rotated bar, because it is corner to corner
   whatever the button measures: a fixed angle would be wrong the moment the
   type size changed.

   The band sits across the middle of the gradient's axis, so it runs at right
   angles to the direction named below - `to bottom right` draws the "/" that
   a no-entry sign uses, and `to top right` would draw the other one. Easy to
   get backwards; the direction here is the one to keep. */
:root.no-dm-badges .btn--badges::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    pointer-events: none;
    background: linear-gradient(
        to bottom right,
        transparent calc(50% - 1px),
        var(--ink-soft) calc(50% - 1px),
        var(--ink-soft) calc(50% + 1px),
        transparent calc(50% + 1px)
    );
}

/* The answer sits in a band of FIXED height, and the band is what lines the
   row up. Aligning the stack alone is not enough: the three answers are
   deliberately three different sizes - Y is 1.2em and Pos is 0.72em - so
   top-aligned they would share a top edge and sit on three different
   baselines, which reads as crooked rather than as level. An equal height and
   line-height centres each glyph inside its own band, so whatever the size,
   the middle of the answer is at the same place in every column.

   Height in rem against --grid-scale, deliberately not in em: em would follow
   the answer's own font-size and give the three different bands, which is the
   whole thing being fixed. Empty cells keep their band too - it costs nothing,
   because the event cell is always taller, and it means the row does not
   change shape as the first answer goes in. */
.cell__v {
    position: relative;
    z-index: 1;
    display: block;
    height: calc(1.4rem * var(--grid-scale));
    line-height: calc(1.4rem * var(--grid-scale));
}

/* Alternating rows, so the eye can track across 22 columns to the right
   person - and back left to the event that answer belongs to. The band runs
   the full width, the three pinned columns included; one that stops short of
   the event column cannot be followed to the thing it identifies.
   The class is put on by the template rather than left to nth-child(even),
   because a year line is a row too and would take a turn in the alternation,
   shifting every row below it. */
.grid tbody tr.row--alt td:not(.cell--you):not(.tot) { background-color: var(--row-alt); }
.grid tbody tr.row--alt .ev        { background-color: var(--ev-alt); }
.grid tbody tr.row--alt .cell--you { background-color: var(--you-alt); }
.grid tbody tr.row--alt .tot       { background-color: var(--tot-alt); }

/* --- what you have not answered ---
   The one thing on the sheet asking the reader to do something, so it is the
   one thing allowed to nag. It marks your own cell rather than the row: the
   row is everybody's, and what is wanted is your answer in particular.
   After the striping and specific enough to beat it, or the band would cover
   it on every other line. */
/* The wash is a background-IMAGE, not a translucent background-color. This
   cell is position: sticky, so every other member's column scrolls underneath
   it - and a background-color at 0.16 alpha replaces the opaque --you-bg
   rather than sitting on it, leaving the rest of the side visibly sliding
   about behind your own answer. An image layers over whichever opaque colour
   won, striped row or not, which is the same reason .ev--typed is drawn this
   way. */
.grid tbody tr.row--needs-you .cell--you {
    background-image: linear-gradient(rgb(var(--needs) / 0.16), rgb(var(--needs) / 0.16));
    box-shadow: inset 0 0 0 2px rgb(var(--needs) / 0.75);
}
/* No glyph in it. "?" is what the spreadsheet wrote for a maybe, so putting
   one in an empty cell would read as an answer rather than as the lack of
   one - to the very people who have spent years reading it that way. */

/* Hover, restated after the striping and specific enough to beat it on an
   even row. */
.grid tbody .cell--you:hover,
.grid tbody tr.row--alt .cell--you:hover,
.grid tbody tr.row--needs-you .cell--you:hover { background-color: var(--green-mid); }

/* --- where the year turns over ---
   A season runs September to August, so Sat 5 Dec and Sat 9 Jan sit next to
   each other in the list with nothing between them to say twelve months have
   passed. The spreadsheet had the same gap. */
.grid .yearline td {
    background-color: var(--navy);
    padding: 0;
    height: calc(1.5rem * var(--grid-scale));
    border-right: 0;
}
/* Pinned to the left edge so the year is still readable when the grid is
   panned to the far side of the season - the same reason the names are. */
.yearline__label {
    position: sticky;
    left: 0;
    display: inline-block;
    padding: 0.15rem 0.7rem;
    color: #fff;
    font-weight: 700;
    font-size: 0.95em;
    letter-spacing: 0.1em;
}

/* On a phone the event column has to give way, or the three pinned columns
   eat the whole screen and nobody else is visible at all.

   8.5rem rather than the 7.5rem it gave way to at first, which was a shade too
   mean: measured at 390x844, the longest event name on the sheet wrapped to
   79px of title against 63px now, and every squeezed name in the side's own
   screenshot - Wellesbourne Beaver Scouts, Walking Weekend - lost a line. It
   costs nothing that shows at 390px: three member columns are fully on screen
   at 7.5rem and still three at 9rem, because a member column is only 2.95rem
   and the room left over changes from 159px to 148px - the fourth column is a
   partly visible sliver either way. 9rem would take the worst name to 47px if
   the event column is ever wanted wider still. Below 375px it IS a column, and
   the rule underneath this block takes it back. */
@media (max-width: 40rem) {
    :root {
        --ev-w:  calc(8.5rem * var(--grid-scale));
        --tot-w: calc(4.9rem * var(--grid-scale));
    }
    .ev__where { display: none; }
    /* Move the line size, not the rule: the indented breakdown is a fraction
       of this, and re-declaring `.tot span` here is what made the two equal. */
    .grid .tot { --tot-line: 0.8em; }
    /* The word stays. A green tick on its own is a mark you have to be taught -
       and whether the dance out is actually on is the thing people check before
       working out if they are free, so it is the last thing to give up its
       words. It wraps to its own line when the date leaves no room, which costs
       a line only on the rows that are confirmed. */
    .ev__conf      { padding: 0.12em 0.35em; letter-spacing: 0.04em; }
    .ev__conf-word { font-size: 0.95em; }

    /* The same trim on the ownership stamp, and for a sharper reason: it
       is long enough to wrap to three lines in a 122px event column, and
       three lines of it made the row 134px on a 390px phone - a whole
       dance out of vertical room spent on a badge. Trimmed it wraps to two
       and the row comes back to 96px. The words stay: "you are looking
       after this" is the point of the thing, and a star on its own is a
       mark somebody has to be taught. */
    .ev__mine      { padding: 0.12em 0.35em; letter-spacing: 0.02em; font-size: 0.68em; }
    .ev__mine-mark { font-size: 0.95em; }
}

/* Below about 375px, back to being mean about it. 7.5rem is not a round number
   somebody liked: it is what leaves room for three member columns beside the
   pinned block on a 360px phone, which is most of the cheap Android screens.
   Measured there: 129px of room left at 7.5rem against 125px at 8rem, and a
   member column is 2.95rem - so the third person falls off the screen for the
   sake of four pixels of event name. At 375px and up the wider column is free,
   which is why it is only taken back here. */
@media (max-width: 23.4rem) {
    :root {
        --ev-w: calc(10rem * var(--grid-scale));
    }
}

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

.picker {
    position: fixed;
    left: 50%;
    bottom: 1rem;
    transform: translateX(-50%);
    width: min(23rem, calc(100vw - 1.5rem));
    background: var(--paper);
    border: 1px solid var(--line-strong);
    border-radius: 12px;
    box-shadow: 0 12px 32px rgba(0,0,0,0.18);
    padding: 0.85rem;
    z-index: 80;
}
.picker__who     { font-weight: 650; margin-bottom: 0.55rem; }
.picker__answers { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.35rem; }

.ans {
    font: inherit;
    font-weight: 600;
    font-size: 0.9rem;
    padding: 0.65rem 0.2rem;      /* generous: this is the main tap target */
    border: 1px solid var(--line-strong);
    border-radius: 7px;
    background: var(--paper);
    cursor: pointer;
}
.ans:hover        { border-color: var(--green); }
.ans--yes.is-on   { background: var(--yes);   color: #fff; border-color: var(--yes); }
.ans--no.is-on    { background: var(--no);    color: #fff; border-color: var(--no); }
.ans--maybe.is-on { background: var(--maybe); color: #fff; border-color: var(--maybe); }
/* Not one of the three answers - it dismisses the picker. Outlined rather than
   filled so it never looks like a fourth thing you could be choosing. */
.ans--close       { color: var(--green); border-color: var(--green); font-weight: 650; }
.ans--close:hover { background: var(--green-soft); }

.picker__guests { margin-top: 0.6rem; font-size: 0.9rem; }
.picker__guests label { display: flex; align-items: center; gap: 0.4rem; }
.picker__guests output { min-width: 1.4rem; text-align: center; font-weight: 650; }

.picker__note { display: block; margin-top: 0.55rem; font-size: 0.85rem; color: var(--ink-soft); }
.picker__note input {
    display: block; width: 100%; font: inherit; margin-top: 0.2rem;
    padding: 0.4rem; border: 1px solid var(--line-strong); border-radius: 6px;
}

.picker__history {
    margin-top: 0.7rem; border-top: 1px solid var(--line); padding-top: 0.5rem;
    font-size: 0.8rem; color: var(--ink-soft); max-height: 9rem; overflow-y: auto;
}
.picker__history ul { margin: 0; padding: 0; }
.picker__history li { list-style: none; margin: 0 0 0.3rem; }
.picker__history b  { color: var(--ink); font-weight: 600; }

/* ---------- the build stamp ---------- */

/* Small, grey and centred, at the foot of every page. Not fixed to the bottom
   of the window: it is worth a glance twice a year, and a line pinned over the
   grid would cost a row of the sheet every time it is not. The grid already
   leaves 4rem below itself, so this lands in space that was there anyway. */
.foot {
    padding: 1rem 1rem 1.4rem;
    /* A row rather than centred text, since there are two things in it now.
       Wrapping, because on a narrow phone the number and the button do not fit
       on one line and the button is the part that must stay tappable. */
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.5rem 0.75rem;
    font-size: 0.75rem;
    color: var(--ink-soft);
}
/* On a grid page the table now ends at the bottom of the viewport, so this
   line sits under it permanently rather than below the fold - and every pixel
   of chrome on those pages is a pixel of grid. */
body.is-grid .foot { padding: 0.35rem 1rem 0.45rem; }
/* A title tooltip never appears on a phone, so the cursor is the only hint
   that there is more behind it - and only where there is. */
.foot__version[title] { cursor: help; }

/* A real bordered button rather than btn--quiet, which in small print beside a
   version number is indistinguishable from the version number. Sized down to
   the footer's own type, so it sits with the line rather than shouting over it. */
.foot__log {
    font-size: 0.75rem;
    padding: 0.2rem 0.55rem;
}

/* ---------- what has changed ---------- */

.rels {
    margin: 1.25rem 0 0;
    padding: 0;
    list-style: none;
}

.rels__one {
    padding: 0 0 0.9rem;
    margin: 0 0 0.9rem;
    border-bottom: 1px solid var(--line);
}

.rels__one:last-child { border-bottom: 0; }

/* The one you are running. A left edge rather than a wash, so it marks the
   whole block without touching the words - the same shape a maybe's row uses
   on the grid, and for the same reason. */
.rels__one--now {
    border-left: 4px solid var(--green);
    padding-left: 0.8rem;
}

.rels__head {
    margin: 0 0 0.4rem;
    font-size: 1.05rem;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.4rem 0.6rem;
}

.rels__when {
    font-weight: 400;
    font-size: 0.85rem;
    color: var(--ink-soft);
}

.rels__now {
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    background: var(--green);
    color: #fff;
    border-radius: 999px;
    padding: 0.1rem 0.5rem;
    white-space: nowrap;
}

.rels__lines {
    margin: 0;
    padding-left: 1.1rem;
    line-height: 1.5;
    /* Stated, because a ul nested inside an ol takes the browser's second-level
       marker and every line came up with a hollow ring in front of it. */
    list-style: disc;
}

.rels__lines li { margin: 0 0 0.3rem; }

/* ---------- install hint ---------- */

.install-hint {
    position: fixed; bottom: 0.8rem; left: 50%; transform: translateX(-50%);
    background: var(--ink); color: #fff; border-radius: 8px;
    padding: 0.6rem 0.9rem; display: flex; align-items: center; gap: 0.7rem;
    font-size: 0.85rem; z-index: 70; max-width: calc(100vw - 2rem);
}
/* A class setting display: flex outranks the browser's [hidden] rule, so the
   hint would sit there permanently - empty, on every platform. */
.install-hint[hidden] { display: none; }
.install-hint .btn--quiet { color: #b9c6bf; }

/* ---------- somebody else has changed this page ----------
   Only ever seen when the refresh is being held back - there is unsaved typing
   on the page, or the answer picker is open - so it is rare and it explains
   itself. The same shape as the install hint, and the same [hidden] trap:
   a class setting display outranks the browser's own rule for the attribute. */

.live-note {
    position: fixed; bottom: 0.8rem; left: 50%; transform: translateX(-50%);
    background: var(--ink); color: #fff; border-radius: 8px;
    padding: 0.6rem 0.9rem; display: flex; align-items: center; gap: 0.7rem;
    font-size: 0.85rem; z-index: 70; max-width: calc(100vw - 2rem);
}
.live-note[hidden] { display: none; }
.live-note .btn--quiet { color: #b9c6bf; }

/* The line explaining why the grid is not the page somebody left. */
.gridwrap__gone { margin: 0.8rem 1rem; }

/* ---------- panels, forms, the other pages ---------- */

.panel {
    background: var(--paper); border: 1px solid var(--line); border-radius: 10px;
    padding: 1.1rem 1.2rem; margin: 1rem auto; max-width: 44rem;
}
.panel h1 { margin-top: 0; font-size: 1.3rem; }
.panel h2 { font-size: 1rem; margin: 1.4rem 0 0.4rem; }

.field { display: block; margin: 0.7rem 0; }
.field > span { display: block; font-size: 0.83rem; color: var(--ink-soft); margin-bottom: 0.2rem; }
/* Not checkboxes: a full-width checkbox stretches its hit area across the row
   and leaves the box itself floating away from the words it belongs to. */
.field input:not([type="checkbox"]):not([type="radio"]),
.field textarea, .field select {
    font: inherit; width: 100%; padding: 0.5rem;
    border: 1px solid var(--line-strong); border-radius: 6px; background: var(--paper);
}

/* A checkbox and its words on one line, box first. */
.check {
    display: flex;
    align-items: flex-start;
    gap: 0.55rem;
    margin: 0.8rem 0;
    cursor: pointer;
}
.check input[type="checkbox"] {
    flex: none;
    width: 1.05rem;
    height: 1.05rem;
    margin: 0.15rem 0 0;
    accent-color: var(--green);
}
.check input[type="checkbox"]:disabled { cursor: not-allowed; }
.check > span { flex: 1; }
.check small { display: block; color: var(--ink-soft); font-weight: 400; margin-top: 0.1rem; }
.check:has(input:disabled) { cursor: default; opacity: 0.75; }
.field textarea { min-height: 6rem; resize: vertical; }
/* Bottom-aligned so a button sitting beside a field lines up with the input
   rather than with the label above it. */
.field--row { display: flex; gap: 0.8rem; align-items: flex-end; flex-wrap: wrap; }
.field--row > * { flex: 1; }
/* A button is as wide as its words, and carries the same vertical margin as a
   field so their bottom edges meet. */
.field--row > .btn { flex: none; margin: 0.7rem 0; }

/* For a field with a button beside it, rather than a pair of equal fields:
   everything sizes to its content so the button sits next to what it acts on
   instead of being shoved to the far edge of the panel. */
.field--row--tight > * { flex: none; }
.field--row--tight > .field { min-width: 14rem; }

.btn--primary { background: var(--green); color: #fff; border-color: var(--green);
                padding: 0.55rem 1.1rem; font-weight: 600; }
.btn--danger  { color: var(--no); border-color: var(--no); }

.notice  { background: var(--green-soft); border: 1px solid var(--navy-mid); border-radius: 7px; padding: 0.7rem 0.9rem; }
.warning { background: #fdf0ef; border: 1px solid #edc4c0; border-radius: 7px; padding: 0.7rem 0.9rem; }
.muted   { color: var(--ink-soft); font-size: 0.87rem; }
.dl      { display: grid; grid-template-columns: 8rem 1fr; gap: 0.35rem 1rem; margin: 0; }
.dl dt   { color: var(--ink-soft); font-size: 0.87rem; }
.dl dd   { margin: 0; }

/* A LEGEND OF SHORT PAIRS RUNS ALONG A LINE, IT DOES NOT STACK. The dance
   list's five marks in the two-column grid above cost five rows - measured at
   138px on a 390px phone, for twenty-two words, inside a panel that came to
   579px, which is 69% of that phone's screen and 46% of the whole page. The
   pairs are a letter and one or two words: read along, not looked up, so the
   grid was buying a column of alignment nobody needed at the price of the
   page. Nothing is hidden by this - every word still prints, which is the
   point, since a colour nobody has been told about is a colour that gets
   ignored.

   It must come AFTER `.dl`: both are a single class, so this wins on order
   alone and would silently lose if it were moved above. `dt` here is two
   classes and an element against `.dl dt`'s one, so the colour and size do
   take. */
.dl--inline                  { display: block; }
.dl--inline dt,
.dl--inline dd               { display: inline; }
.dl--inline dt               { font-weight: 650; color: var(--ink); font-size: inherit; }
.dl--inline dd::after        { content: ' \00B7 '; color: var(--ink-soft); }
.dl--inline dd:last-of-type::after { content: ''; }

/* THE FOOT OF A DANCE LIST IS TIGHT, because it is below the one thing the page
   is for and everything it takes is taken off the sheet.

   It was a `<details>` folded shut for one release, which was the wrong answer:
   a key you have to open is a key you have to think about, where one you can
   see is read at a glance and costs nothing to ignore. Going off the bottom of
   the screen is fine - it is a scrolling page.

   So the room comes out of the SPACING, which is where it was all along.
   Measured on the panel at 390x844, the words were never the bulk of it: two
   `.panel h2` top margins at 1.4rem, two default `<p>` margins at 1em top and
   bottom each, the panel's own 1.1rem top and bottom, `.trail`'s 1rem bottom
   and 0.3rem of padding on every row. None of that is content. Every number
   below is a fraction of what `.panel` and `.trail` give a page that is mostly
   prose - which those two rightly are, and this is not.

   Scoped to `.sheetfoot` and nowhere else, so no other panel in the app moves.
   Each selector carries the element as well as the class where it is
   overriding one that does - `.panel h2` is a class and an element, and a bare
   `.sheetfoot h2` would be the same weight and win only on order, which is the
   trap this stylesheet has been caught by four times. */
/* AND IT SITS UNDER THE LEFT-HAND END OF THE SHEET, not in the middle of the
   window. `.panel` is `margin: 1rem auto` with a 44rem cap, which is right for
   a page of prose and wrong under a grid: on a 1920px monitor the key was
   centred 685px from the left edge, under a sheet that starts at 0 and ends at
   1220, so the two did not look like parts of the same page. The gutter is
   kept on both sides for the phone, where the panel is wider than the window
   and would otherwise touch both edges; the centring is what goes.
   Reported from a PC, where the same fault is worse than on a phone because
   there is more window to be adrift in. */
.sheetfoot                   { padding: 0.7rem 1.2rem 0.8rem;
                               margin: 0.7rem 0.6rem 1rem; }
.sheetfoot h2                { font-size: 1rem; margin: 0.9rem 0 0.25rem; }
.sheetfoot > h2:first-of-type { margin-top: 0; }
.sheetfoot p                 { margin: 0.3rem 0 0; }
.sheetfoot .dl--inline       { margin: 0.15rem 0 0; }
.sheetfoot .trail            { margin: 0.2rem 0 0; }
.sheetfoot .trail li         { padding: 0.18rem 0; }
.pre     { white-space: pre-wrap; font: inherit; background: #fafbfe; border: 1px solid var(--line);
           border-radius: 6px; padding: 0.7rem; max-height: 24rem; overflow: auto; }

.mine__item { display: flex; align-items: center; gap: 0.8rem;
              border-bottom: 1px solid var(--line); padding: 0.75rem 0; }
.mine__what { flex: 1; }

/* A device's name IS the control, so the box has to sit where the name sat -
 * bold, at the top of the row - rather than looking like a form somebody has
 * to go and find. It wraps under itself on a narrow screen, where the Save
 * would otherwise squeeze the name to a couple of characters. The min-width
 * is what stops flex shrinking the input below usefulness in the row it
 * shares with the Sign out button. */
.device__name { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
/* Capped, or on a laptop the box runs the width of the panel and reads as a
 * search bar rather than as a name. Still 100% of what it is given on a
 * phone, where the basis is what matters. */
.device__name label { flex: 1 1 9rem; min-width: 0; max-width: 20rem; }
.device__name input { width: 100%; font-weight: 600; }

/* ---------- accessibility ---------- */

@media (prefers-reduced-motion: reduce) {
    * { transition: none !important; animation: none !important; }
}

@media (forced-colors: active) {
    .cell--edited-self::before,
    .cell--edited-other::before { display: none; }
    /* Drawn in full here and nowhere else. The two rules above hide the wash,
       because forced colours replace the backgrounds it works by, so without
       this there would be no sign at all that a cell had just been changed. */
    .cell[data-edited]::after {
        content: "";
        position: absolute;
        top: 0; right: 0;
        border: calc(0.28rem * var(--grid-scale)) solid transparent;
        border-top-color: Highlight;
        border-right-color: Highlight;
        pointer-events: none;
    }
    .row--needs-you .ev         { box-shadow: inset 3px 0 0 Highlight; }
}

/* ---------- member pages ---------- */

.badge {
    display: inline-block; vertical-align: middle;
    font-size: 0.7rem; font-weight: 650; letter-spacing: 0.02em;
    text-transform: uppercase;
    padding: 0.12rem 0.45rem; border-radius: 99px;
    background: var(--green-soft); color: var(--green);
    border: 1px solid var(--green-mid);
}
/* The sheet's own mark. Column C of the workbook flags a musician with a
   single M, and a column of them reads as a column of marks rather than the
   word "musician" repeated down the page. The rest of the word is still there
   for a screen reader, and the title carries it on hover. */
.badge--mus { background: #fdf6db; color: var(--gold-deep); border-color: #e8d79a;
              min-width: 1.5rem; text-align: center;
              padding-left: 0.3rem; padding-right: 0.3rem; }
/* The other half of the same fact, and the commoner one. Its own hue so a
   column of people reads at a glance - who dances, who plays, who does both -
   and a single letter for the same reason the musician badge is one: a column
   of marks rather than the word repeated down the page. */
.badge--dan { background: #e7eafa; color: var(--navy-deep); border-color: #c6cef0;
              min-width: 1.5rem; text-align: center;
              padding-left: 0.3rem; padding-right: 0.3rem; }
.badge--off { background: #eef0f5; color: var(--ink-soft); border-color: var(--line); }

ul.plain { list-style: none; margin: 0.3rem 0; padding: 0; }
ul.plain li { padding: 0.25rem 0; border-bottom: 1px solid var(--line); }
ul.plain li:last-child { border-bottom: 0; }

.field small { display: block; font-weight: 400; margin-top: 0.15rem; }

/* Member names in the grid head are links to their page, but should not shout
   about it - the grid is for answering, not for navigating. */
.grid a.vert { color: inherit; text-decoration: none; }
.grid a.vert:hover { text-decoration: underline; text-decoration-color: var(--green); }

/* Links inside content panels take the app's green rather than browser blue,
   which was the only thing on these pages not from the palette. */
.panel a { color: var(--green); text-decoration-color: var(--green-mid); }
.panel a:hover { text-decoration-color: var(--green); }
.panel .btn--primary { color: #fff; }
.panel .dl a { text-decoration: none; }
.panel .dl a:hover { text-decoration: underline; }

/* A date picker never needs the full width of a panel. */
.field input[type="date"] { max-width: 14rem; }

/* On a phone the badge carries the brand on its own, and the tabs get the
   width the wordmark was using. The link keeps its name through aria-label. */
/* The wordmark gives way in two steps as the bar narrows: the side's name
   goes first, leaving "Ticksheet", and then that goes too. The tabs are what
   the bar is for. */
@media (max-width: 56rem) {
    .topbar__word-side { display: none; }
}
@media (max-width: 30rem) {
    .topbar__word { display: none; }
    .topbar { gap: 0.6rem; padding-left: 0.6rem; padding-right: 0.6rem; }
    /* The word goes and the "?" stays, the same trade the wordmark makes: the
       tabs are what the bar is for. Taken off the screen rather than out of the
       page - display: none would take it out of the accessibility tree too, and
       the link would be a bare "?" with the "?" itself aria-hidden. */
    .topbar__help-word {
        position: absolute; width: 1px; height: 1px; overflow: hidden;
        clip-path: inset(50%); white-space: nowrap;
    }
    .topbar__end { gap: 0.5rem; }
}

/* ---------- the event types page ---------- */

.notice--bad { background: #fdecea; border-color: #e6b2ac; color: #7a231c; }

/* The preview strip above a dance list. A row rather than a paragraph, so the
   way out sits with the words instead of under them, and margin-left: auto on
   the button because the words are the thing being read. Tight vertically -
   it is above a grid that wants the height, and it is only on screen while
   somebody is deliberately previewing. */
.notice--preview {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin: 0.5rem 0.9rem;
    padding: 0.45rem 0.7rem;
}
.notice--preview .btn { margin-left: auto; }

/* One type per line on a desktop, stacking on a phone. The fields keep their
   own labels rather than a header row, which would strand them the moment the
   line wraps. */
.typerow {
    display: flex;
    flex-wrap: wrap;
    align-items: end;
    gap: 0.4rem 0.8rem;
    padding: 0.55rem 0;
    border-top: 1px solid var(--line);
}
.typerow .field         { margin: 0; flex: 1 1 8rem; }
.typerow .field--narrow { flex: 0 0 4.6rem; }
/* A fixed width, because this is the only thing on the row whose text length
   varies - "held by 1" against "nobody holds it" - and while it sized itself
   the name field took up the slack. Every row was then a different shape and
   the Save buttons stepped in and out down the page. */
.typerow__count         { color: var(--ink-soft); font-size: 0.85rem; padding-bottom: 0.45rem;
                          flex: 0 0 8.5rem; text-align: right; }
/* Fixed, because the add row has one button where the others have two, and a
   flexible box let the name field swallow the difference - the buttons then
   stepped out on the last row. */
.typerow__do            { flex: 0 0 7.2rem; padding-bottom: 0.1rem;
                          display: flex; align-items: center; gap: 0.6rem; }

/* THE BIN ON A LIST WHOSE ROWS ARE SAVED TOGETHER - the traditions and the
   types. A checkbox, ticked now and acted on when the list is saved, so that
   Cancel cancels a removal; the icon is what it looks like, because a bare box
   beside a name reads as neither a control nor a state. Grey until it is going
   to be used, red once it is.

   A FIXED SLOT rather than margin-left:auto, so every bin down the page is in
   one column - the count beside it is fixed width for the same reason, and a
   row whose controls move as its text changes is what makes a page look untidy
   however tidy each row is alone.

   The dance list has its own .listrow__bin doing this job, sized against the
   grid zoom because it lives in a table cell. When that branch lands the two
   want folding together: this is the base, and that is the size. */
.binbox {
    flex: 0 0 1.9rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.2rem;
    margin-bottom: 0.15rem;
    border-radius: 5px;
    cursor: pointer;
}
.binbox .bin {
    width: 1.05rem;
    height: 1.05rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.3;
    stroke-linecap: round;
    color: var(--ink-soft);
}
.binbox:hover .bin { color: #a3231c; }

/* A sibling selector and not :has(), which older iOS Safari does not know -
   and a rule it does not know is a rule it drops in silence. */
.binbox input:checked ~ .bin      { color: #c0271f; stroke-width: 1.9; }
.binbox input:focus-visible ~ .bin { outline: 2px solid var(--navy-mid); outline-offset: 2px; }

/* The add row has nothing to remove and still has to hold the column open. */
.binbox--empty { cursor: default; }

/* One Save at the foot of the list it saves, rather than one at the bottom of
   a page holding two lists - which would read as one form pretending to be
   two, and would put the Types button below the Traditions rows. */
.listform         { margin: 0 0 1.6rem; }
.listform__do     { display: flex; align-items: center; gap: 0.8rem; margin: 0.9rem 0 0; }

/* A colour input styled like a text box becomes a thin line with a swatch
   stretched across it. It wants to be a swatch. */
.typerow input[type="color"] {
    width: 100%;
    height: 2.2rem;
    padding: 0.15rem;
    cursor: pointer;
}

/* The swatch shows the wash at the strength the grid actually uses, so what is
   picked here is what appears there. A full-strength square would look nothing
   like the result. */
.typerow__eg {
    flex: 0 0 7rem;
    align-self: center;
    padding: 0.45rem 0.5rem;
    border-radius: 4px;
    font-weight: 650;
    font-size: 0.85rem;
    color: rgb(var(--type-rgb, 90 100 128));
    background-color: var(--ev-bg);
    background-image: linear-gradient(rgb(var(--type-rgb, 90 100 128) / 0.22),
                                      rgb(var(--type-rgb, 90 100 128) / 0.22));
    box-shadow: inset 5px 0 0 rgb(var(--type-rgb, 90 100 128) / 0.9);
    padding-left: 0.7rem;
}
.typerow__eg--blank { color: var(--ink-soft); background-image: none; box-shadow: none; }





@media (max-width: 40rem) {
    .typerow__eg { flex: 1 1 100%; }
    .typerow .field--narrow { flex: 1 1 5rem; }
    .typerow__count { flex: 1 1 100%; text-align: left; }
}

/* ---------- past events, a year at a time ---------- */

/* Ten years of dance outs is not one list. The years sit under the grid bar
   rather than in the tab strip: they are a filter on this page, not another
   place to be, and there are enough of them to crowd the tabs out. */
.years {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    padding: 0 0.9rem 0.5rem;
}
.year {
    display: inline-flex;
    align-items: baseline;
    gap: 0.35em;
    padding: 0.2rem 0.6rem;
    border: 1px solid var(--line-strong);
    border-radius: 99px;
    font-size: 0.85rem;
    text-decoration: none;
    color: var(--ink);
    background: var(--paper);
    white-space: nowrap;
}
.year:hover      { border-color: var(--green); }
.year.is-active  { background: var(--green); border-color: var(--green); color: #fff; font-weight: 650; }
.year__n         { font-size: 0.8em; color: var(--ink-soft); }
.year.is-active .year__n { color: #c3ccf2; }
.tot__none { display: block; font-size: 0.85em; color: var(--ink-soft); font-style: italic; }

/* A year of the archive with nobody's answers in it is a list, not a grid.
   One column in a max-content table is a narrow strip against a field of
   nothing, so it takes the width instead - up to a readable measure, not the
   whole of a wide screen. */
.grid--list                 { width: 100%; max-width: 44rem; }
.grid--list .head--event,
.grid--list .ev             { width: auto; }
.grid--list .head--event    { height: auto; }
.grid--list .head__logo     { display: none; }

/* ---------- the Look page ---------- */

.setrow {
    display: flex;
    align-items: flex-start;
    gap: 0.9rem;
    padding: 0.5rem 0;
    border-top: 1px solid var(--line);
}
.setrow .field        { margin: 0; }
.setrow .field--narrow{ flex: 0 0 4.6rem; }
.setrow .field--word  { flex: 0 0 9rem; }
.setrow input[type="color"] { width: 100%; height: 2.2rem; padding: 0.15rem; cursor: pointer; }
.setrow__what         { display: flex; flex-direction: column; padding-top: 0.1rem; }
.setrow__what small   { color: var(--ink-soft); }

/* Each colour shown the way the grid actually draws it. A full-strength
   square would say nothing useful about a wash laid at a fifth strength
   behind a letter. */
.setrow__eg {
    flex: 0 0 4.2rem;
    text-align: center;
    padding: 0.35rem 0.2rem;
    border: 1px solid var(--line);
    border-radius: 4px;
    font-weight: 800;
    font-size: 1.05rem;
    background: var(--paper);
}
.setrow__eg--yes,
.setrow__eg--no,
.setrow__eg--maybe     { color: var(--c); }
.setrow__eg--needs     { min-height: 2.2rem; background: rgb(from var(--c) r g b / 0.16);
                         box-shadow: inset 0 0 0 2px var(--c); }
.setrow__eg--editSelf,
.setrow__eg--editOther { color: var(--ink); background: rgb(from var(--c) r g b / 0.34); }
.setrow__eg--confirmed { color: #fff; background: var(--c); font-size: 0.62rem;
                         letter-spacing: 0.06em; }

.visually-hidden {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
}

/* The colour as the grid draws it, beside the colour being picked. They are
   different things - a fade tint is a wash at a third strength - and without
   a word under each they read as one swatch disagreeing with itself. */
.setrow__pair       { display: flex; flex-direction: column; align-items: center; gap: 0.15rem; flex: none; }
/* Inside a column the swatch would stretch to the height of the row. It is a
   sample of a cell, so it keeps a cell's proportions. */
.setrow__pair .setrow__eg     { flex: none; width: 4.2rem; height: 2.2rem;
                                display: flex; align-items: center; justify-content: center; }
.setrow__pair .field--narrow  { width: 4.6rem; height: 2.2rem; display: block; }
.setrow__pair                 { justify-content: flex-start; }
.setrow__pair small { font-size: 0.72rem; color: var(--ink-soft); }
.setrow .field--narrow { flex: 0 0 4.6rem; width: 4.6rem; }
.setrow input[type="number"] { width: 100%; }
.setrow__pick { width: 4.6rem; height: 2.2rem; padding: 0.15rem; cursor: pointer;
                border: 1px solid var(--line-strong); border-radius: 5px; background: var(--paper); }

/* An office of the side. Its own colour, so it does not read as the Admin
   badge beside it - one is a permission, the other is a job. */
.badge--role { background: #ede4f7; border-color: #c9b0e4; color: #5a2d86; }
/* Stands in for the Order field the add rows do not have, so their name box
   is the same width as every row above it. */
.typerow__gap { flex: 0 0 4.6rem; }

/* --- an event split into parts ---
   One dance out, so it reads as one block: the line between its rows goes,
   and the parts after the first are indented under the name. The event's own
   colour bar runs unbroken down the side of the whole group. */
/* The rule between rows is a border-BOTTOM on the cell above, so dropping
   border-top did nothing at all - the line stayed exactly where it was.
   Taking the bottom off every part but the last is what closes the group. */
.grid .ev--part-first,
.grid .ev--part-more            { border-bottom: 0; padding-top: 0.15rem; padding-bottom: 0.15rem; }
.grid .ev--part-last            { border-bottom: 1px solid var(--line); padding-bottom: 0.55rem; }
.grid .ev--part-first           { padding-top: 0.55rem; }
/* The browser hides a [hidden] element with display:none from its own
   stylesheet, and any author rule at all beats that - .ev__when sets
   display:flex, so the attribute was on the element and the date on screen. */
.ev__when[hidden]               { display: none; }
.ev--part-more .ev__part,
.ev--part-first .ev__part       { margin-left: 0.15rem; padding-left: 0.55rem;
                                  border-left: 2px solid var(--line-strong); }
/* Every other event cell is aligned to the top, because the title is the thing
   the eye runs down and a column of titles has to start on one line. A later
   part carries a name and nothing else, and the row is as tall as its answers,
   so top-aligning it left the label floating above the Y it belongs to.
   Centred, it sits level with its own row. */
.grid .ev--part-more            { vertical-align: middle; }
/* The first part now follows the venue rather than the title, so it needs a
   little air above it: without one it reads as a second line of the address. */
.ev--part-first .ev__part       { margin-top: 0.15rem; }

/* A tick that says "and not this one" - no colour on a type, remove this part
   on the next Save. It sits in a row of fields, so it lines up with their
   boxes rather than with their labels. */
.typerow__none {
    flex: 0 0 7.5rem;
    align-self: end;
    padding-bottom: 0.45rem;
    font-size: 0.85rem;
    white-space: nowrap;
}

/* The Save for the whole form, parts included, at the foot of everything it
   saves. It used to sit above the parts, which read as though the parts were
   somewhere else and saved by something else - and they were. */
.formdo {
    margin-top: 1.2rem;
    padding-top: 0.9rem;
    border-top: 1px solid var(--line);
}

/* The colour, and the tick that turns it off.
   The tick rides on the "Colour" label line rather than under the swatch: a
   row of its own made the whole row 157px tall and wrapped the count onto a
   second line, and beside the swatch it pushed the row past the panel. */
.typerow__colour {
    flex: 0 0 5.8rem;
    display: flex;
    flex-direction: column;
}
.typerow__colourhead {
    display: flex;
    align-items: baseline;
    gap: 0.3rem;
    font-size: 0.78rem;
    color: var(--ink-soft);
    margin-bottom: 0.2rem;
}
.typerow__off {
    display: inline-flex;
    align-items: baseline;
    gap: 0.15rem;
    cursor: pointer;
}
.typerow__off input { margin: 0; }

/* A trail of edits. One line each, the date pushed to the right so the
   sentences line up on the left where they are read. */
.trail          { list-style: none; margin: 0.4rem 0 1rem; padding: 0; }
.trail li       { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.35em;
                  padding: 0.3rem 0; border-bottom: 1px solid var(--line); font-size: 0.92rem; }
.trail li:last-child { border-bottom: 0; }
.trail__when    { margin-left: auto; color: var(--ink-soft); font-size: 0.85em; white-space: nowrap; }
.trail em       { color: var(--ink-soft); }

/* ---------- the dances grid ----------
   The ticksheet's grid with the repertoire down the side, so it inherits the
   frozen panes, the zoom and the striping and only says what is different. */

/* Wider than an answer: a selection box has to hold "Lots of practice" and a
   chevron. The dance column is wider than an event's for the same reason -
   "Upton-on-Severn Stick (Chingford version)" is a name, not a heading. */
/* Wider than the events grid's event column, because the tradition and the
   type are written out under the name rather than shown as the side's
   two-letter codes. 15rem holds the longest pair - Bledington Handkerchief -
   on one line without the column taking room the answers want. */
/* Every one of these is a calc against --grid-scale, the same as the root
   definitions they override. Written as plain rem values they took the zoom
   away from this grid entirely: the words got smaller and the columns did not,
   so zooming out gained nothing and 56 dances by 22 people could not be made
   to fit. A width variable that does not carry the scale is not a width
   variable, it is a constant. */
.grid--dances {
    --ev-w:  calc(15rem  * var(--grid-scale));
    --you-w: calc(8.5rem * var(--grid-scale));
    --tot-w: calc(8.5rem * var(--grid-scale));
}

/* The totals sit at the end of the row here and the COLUMN is not pinned - see
   the markup for why. Both halves of the horizontal pinning have to be undone:
   static, or it stays stuck to the left edge, and the left offset removed, or
   a static cell is pushed sideways by it. The right border that separated the
   pinned block from the answers goes too; there is nothing to its right to
   separate.

   THE HEADER STAYS STICKY TO THE TOP, and it did not. `position: static` on
   both undid the vertical pinning as well as the horizontal, so scrolling the
   repertoire down carried the words "Who can dance it" away while every
   member's name stayed put - the same fault, from the same one-line cause, as
   the dance list's. Only `left` is given back here; `.grid .head` keeps doing
   the rest. */
.grid--dances .tot {
    position: static;
    left: auto;
}
.grid--dances .head--tot { left: auto; }
.grid--dances .tot,
.grid--dances .head--tot {
    border-right: 1px solid var(--line);
}
/* On a phone, 15rem was over half the screen for names that do not need it:
   216px of the 390px, with 52px left over, so nothing of anybody else was on
   screen beside your own column. Measured at 390x844 across the whole
   repertoire:

     15rem  216px, 52px left,  10 dances on screen,  0 names wrap,  0 chips wrap
     12rem  173px, 95px left,  10 dances on screen,  2 names wrap,  0 chips wrap
     11rem  158px, 109px left, 10 dances on screen,  5 names wrap,  6 chips wrap
     10rem  144px, 124px left,  8 dances on screen, 10 names wrap, 22 chips wrap

   12rem then: it gives back 43px and costs two names out of fifty-six a second
   line - Upton-on-Severn Stick (Chingford version) was never going to fit
   anyway. Narrower is where it turns: 10rem would put the next member's whole
   column on screen and takes two dances off it instead, with the chips wrapping
   under each other on nearly every row. This grid is read across all
   twenty-two people rather than one at a time, and the zoom is there for
   anybody who wants more of them at once, so the tidy rows won. */
@media (max-width: 40rem) {
    .grid--dances {
        --ev-w: calc(12rem * var(--grid-scale));
    }
}

.grid--dances .cell              { width: calc(8.5rem * var(--grid-scale)); padding: 0.15rem 0.2rem; }
.grid--dances .ev                { vertical-align: middle; }
.grid--dances .ev__when          { gap: 0.35rem; }

.dance__trad,
.dance__kind {
    display: inline-block;
    padding: 0 0.3em;
    border-radius: 3px;
    font-size: calc(0.72rem * var(--grid-scale));
    font-weight: 650;
    letter-spacing: 0.02em;
    background: #eef0f5;
    color: var(--ink-soft);
}
.dance__kind { background: var(--green-soft); color: var(--green); }
/* Two written-out names will not always sit on one line. They wrap under each
   other rather than stretching the column to fit the longest pair. */
.grid--dances .ev__when          { flex-wrap: wrap; }

/* Two answers in a cell, written out: how well they know it, and whether they
   want to. The FULL words, wrapping - which is the whole reason the <select>
   had to go, since a closed one renders on one line and is clipped by the
   browser whatever CSS says.
   There is room already: `.grid--dances .cell` above is 8.5rem against the
   events grid's 2.95rem member column, written for the pinned columns and
   applying to every cell - so a dance column is 122px at the default zoom and
   the longest of these, "Lots of practice", fits on one line with room over.
   The wrapping below is the safety net for a narrow zoom, not the usual case.
   No --mem-w override here: it would look like the governing rule and would
   not be one. */
.grid--dances .cell.skill {
    text-align: center;
    line-height: 1.2;
    padding: calc(0.25rem * var(--grid-scale)) calc(0.2rem * var(--grid-scale));
    /* Long enough words to need it. Break-word rather than a hyphen, because
       "Lots of practice" wants breaking at the space and "What's that?!" does
       not want breaking at all. */
    overflow-wrap: break-word;
}
.skill__comp {
    display: block;
    font-size: calc(0.72rem * var(--grid-scale));
    font-weight: 600;
    /* An EMPTY one still holds its line. A block with nothing in it collapses
       to no height at all, so a cell where somebody has said how they feel but
       not how well they know it pulled its preference up into the competence's
       slot - and "Under duress" sitting exactly where "Confident" sits on the
       row above reads as a competence. min-height rather than height, because
       the cell prints the words in full and "Needs teaching" is allowed to
       wrap onto a second line. */
    min-height: calc(0.85rem * var(--grid-scale));
}
/* A footnote to the line above it, not a competing statement. Ink only, no
   background: two full colour scales in one cell would fight. */
.skill__pref {
    display: block;
    margin-top: calc(0.1rem * var(--grid-scale));
    font-size: calc(0.64rem * var(--grid-scale));
    opacity: 0.85;
}

/* A SCALE, at last, rather than a grouping - which is what splitting the eight
   into two questions bought. Competence runs green to red in the order the
   answers themselves are ordered, so the colour and the words cannot disagree
   about which way is up.

   The two greens are exactly CAN_DANCE, so "green means they can dance it" is
   true at a glance. That is the question the totals column answers and the
   reason the constant exists.

   Painted as a background-IMAGE, not a background-colour. Twice this has been
   got wrong. The striping rule is
   ".grid tbody tr.row--alt td:not(.cell--you):not(.tot)" - four classes and
   three elements - so no sane class selector outranks it, and the first two
   attempts set a colour that every striped row quietly threw away. An image is
   a different property: it layers over whatever colour won, the same trick the
   event-type wash uses and for the same reason. */
.grid .skill--comp-confident        { background-image: linear-gradient(#c7e7d3, #c7e7d3);
                                      color: #14532d; }
.grid .skill--comp-nearly-there     { background-image: linear-gradient(#dcf0e3, #dcf0e3);
                                      color: #24603a; }
.grid .skill--comp-more-practice    { background-image: linear-gradient(#fbeecb, #fbeecb);
                                      color: #7a5300; }
.grid .skill--comp-refresher-please { background-image: linear-gradient(#f9e2c8, #f9e2c8);
                                      color: #8a5312; }
.grid .skill--comp-needs-teaching   { background-image: linear-gradient(#fadcd3, #fadcd3);
                                      color: #8a3520; }
.grid .skill--comp-whats-that       { background-image: linear-gradient(#f6cec6, #f6cec6);
                                      color: #7a2417; }
.grid .skill--none                  { color: var(--ink-soft); }

/* Preference: ink only, on the second line. Its own scale, running from a
   positive hue to a muted one, and deliberately quieter than the competence
   above it - it is a footnote to that number, not a rival to it. The two that
   came from the old eight keep their italic, which is what stops "duress" and
   "dislike" reading as just another shade. */
.grid .skill--pref-love         { color: #1c6b3f; font-weight: 650; }
.grid .skill--pref-happy        { color: #2f6b46; }
.grid .skill--pref-dont-mind    { color: #5a6480; }
.grid .skill--pref-under-duress { color: #85601a; font-style: italic; }
.grid .skill--pref-dislike      { color: #8a2f22; font-style: italic; }

/* The third line: whatever this person wanted to add - "only in second
   corner", "not leading", "ask me on the day". Belongs to the opinion rather
   than to any one answer, so it takes no colour from the scale above it: a
   quiet neutral, smaller again, reading as an aside to both lines rather than
   as a third opinion. Empty on almost every cell, and an empty span takes no
   height. */
.grid--dances .cell.skill .skill__note {
    display: block;
    margin-top: calc(0.08rem * var(--grid-scale));
    font-size: calc(0.6rem * var(--grid-scale));
    line-height: 1.15;
    color: var(--ink-soft);
    font-style: italic;
}

.skill.is-saving  { opacity: 0.55; }
.skill.is-failed  { box-shadow: inset 0 0 0 2px rgb(var(--needs)); }

/* Two numbers and a footnote, the same shape as the ticksheet's totals. */
.grid--dances .tot     { font-size: calc(0.78rem * var(--grid-scale)); line-height: 1.35; text-align: left;
                         padding: 0.3rem 0.45rem; }
.grid--dances .tot__n  { display: block; color: var(--ink-soft); }

/* --- what people said ---
   The notes members left with their answers, collated under each part. Set as
   a quiet block rather than as another <dl> row: the lists above are names to
   scan, and these are sentences to read, which is a different job for the eye.
   Indented behind a rule so the block reads as an aside to the numbers above
   without competing with the next heading. */
.says {
    margin: 0.9rem 0 0;
    padding: 0.1rem 0 0.1rem 0.75rem;
    border-left: 3px solid var(--line);
}

.says__head {
    margin: 0 0 0.4rem;
    font-size: 0.82rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

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

.says__item {
    margin: 0 0 0.45rem;
    line-height: 1.4;
}

.says__item:last-child { margin-bottom: 0; }

.says__who { font-weight: 700; }

/* The answer the note was left against, because it changes what the note
   means. Small and outlined rather than filled: it is a label on a sentence,
   not a stamp on a booking, and three filled chips per line would shout. */
.says__ans {
    display: inline-block;
    margin: 0 0.35em;
    padding: 0 0.4em;
    font-size: 0.72em;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    border: 1px solid currentColor;
    border-radius: 3px;
    vertical-align: 0.08em;
}

/* The side's own three colours, the ones an admin picked on the Look page,
   rather than three of my own that would drift the moment somebody changed
   theirs. The server already writes them into the page as --yes, --no and
   --maybe for the grid. */
.says__ans--yes   { color: var(--yes); }
.says__ans--no    { color: var(--no); }
.says__ans--maybe { color: var(--maybe); }

/* The offices, in the guide. A table rather than a list, because what each one
   can do is now a column here instead of a headed section further down the
   page - three things per office read across a row, and read badly as prose. */
.guide__table {
    width: 100%;
    border-collapse: collapse;
    margin: 0.5rem 0 1.1rem;
    font-size: 0.95rem;
}

.guide__table th,
.guide__table td {
    text-align: left;
    vertical-align: top;
    padding: 0.45rem 0.7rem 0.45rem 0;
    border-bottom: 1px solid var(--line);
}

.guide__table thead th {
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

.guide__table tbody th { font-weight: 650; white-space: nowrap; }

/* The middle column is the one with sentences in it, so it takes what is
   spare; the other two are a name and a count and want no more than they need. */
.guide__table tbody td:last-child { white-space: nowrap; }

/* On a phone three columns of prose is unreadable, so each office becomes a
   block: its name, then what it does, then who holds it. The header row goes,
   since there is nothing left for it to sit above. */
@media (max-width: 34rem) {
    .guide__table thead { display: none; }
    .guide__table, .guide__table tbody, .guide__table tr, .guide__table th, .guide__table td {
        display: block;
        width: auto;
    }
    .guide__table tr {
        padding: 0.5rem 0;
        border-bottom: 1px solid var(--line);
    }
    .guide__table th, .guide__table td { border-bottom: 0; padding: 0 0 0.2rem; }
    .guide__table tbody th { font-size: 1rem; }

    /* The count carries its own label once the header row is gone, or "one
       member" sits under a sentence about documents and reads as part of it. */
    .guide__held::before { content: "Held by "; }
}

.grid__empty { padding: 1.2rem; color: var(--ink-soft); }

.gridbar__note { margin: 0; font-size: 0.85rem; }

/* Dances the side no longer does. Still answerable - somebody knowing an old
   dance is a fact that outlasts the side dancing it, and a dance can come back
   - so this is greyed rather than faded: the whole block steps back a shade,
   and every word of it stays readable at full opacity. */
.grid .row--retired .ev {
    /* A background-image, because the row striping is ".grid tbody tr.row--alt
       td:not(...)" - four classes and three elements - and no colour written
       here would ever outrank it. */
    background-image: linear-gradient(#eceef4, #eceef4);
}
.grid .row--retired .ev__title   { color: #5c6580; font-weight: 600; }
.grid .row--retired .dance__trad,
.grid .row--retired .dance__kind { background: #e0e3ec; color: #5c6580; }

/* A member's name at the head of a row is a heading, not a link inside a run
   of prose. ".panel a" underlines everything in a panel, which turned both
   member lists into a page of underlined links rather than a list of people.
   The definition lists opted out of the same rule above; these do too, and
   keep the underline on hover so it still reads as clickable. */
.mine__what > a       { text-decoration: none; }
.mine__what > a:hover { text-decoration: underline; }

/* ---------- the guide ----------
 * One long page, read top to bottom once and then dipped into. What it needs
 * over an ordinary panel is a way to see where the three parts start, and a
 * way in from the top for somebody who already knows which part they want. */
.guide { max-width: 48rem; }

/* Anchors, so a link into a part does not land it under the sticky top bar. */
.guide [id] { scroll-margin-top: 4.5rem; }

/* The part headings carry the weight the h2s in a settings panel do not: they
   are the three answers to "is this me?", and the reader is meant to be able
   to find them while scrolling past. */
/* `.guide h2` and not `.guide__part` alone: the class on its own is one class,
   and `.panel h2 { font-size: 1rem }` is a class and an element, so it won that
   and the part headings had been rendering at exactly the size of an ordinary
   panel heading since they were written - carried entirely by the rule above
   them. Which is the same complaint as the one below it: three levels that all
   looked like one. */
.guide h2.guide__part {
    font-size: 1.3rem;
    margin-top: 2.4rem;
    padding-top: 0.8rem;
    border-top: 2px solid var(--line-strong);
}
.guide__part .badge { vertical-align: 0.15em; margin-left: 0.35rem; }

/* THREE LEVELS, AND THEY HAVE TO LOOK LIKE THREE.
 *
 * The part (h2 above), then the thing being described - a page of the app, or
 * getting started - then a topic within it. Every one of those middle and
 * inner headings was an h3 at 0.98rem in the same green, so "Answering" and
 * "Meetings" carried identical weight and the page read as thirty topics in a
 * row rather than as eight pages with topics under them. Somebody looking for
 * the Dances tab had to read every heading to find it.
 *
 * So the page heading is dark, larger, and ruled underneath - it reads as a
 * section starting. The topic keeps roughly what the old h3 had: smaller,
 * green, no rule. The step between them is deliberately bigger than the step
 * from the part heading to the page heading, because that is the distinction
 * that was missing. */
.guide h3 {
    font-size: 1.08rem;
    font-weight: 700;
    color: var(--ink);
    margin: 1.9rem 0 0.5rem;
    padding-bottom: 0.25rem;
    border-bottom: 1px solid var(--line);
}

/* Straight after a part heading there is no need for the gap: the rule above
   has already said a section is starting. */
.guide__part + h3 { margin-top: 1.1rem; }
.guide .muted + h3 { margin-top: 1.4rem; }

.guide h4 {
    font-size: 0.95rem;
    font-weight: 650;
    color: var(--green);
    margin: 1.15rem 0 0.3rem;
}

.guide p  { margin: 0.5rem 0; }

/* Wider than the .dl elsewhere: these are answer words and level names rather
   than field labels, and "Lots of practice" does not fit in 8rem. */
.guide .dl { grid-template-columns: 10rem 1fr; margin: 0.6rem 0; }
.guide .dl dt { color: var(--ink); font-weight: 600; font-size: 0.9rem; }

.guide__toc {
    display: flex; flex-wrap: wrap; gap: 0.4rem;
    margin: 0.9rem 0 0.4rem;
}
.guide__toc a {
    text-decoration: none;
    font-size: 0.85rem;
    padding: 0.3rem 0.6rem;
    border: 1px solid var(--line-strong);
    border-radius: 99px;
    color: var(--ink);
}
.guide__toc a:hover { border-color: var(--green); color: var(--green); }

.guide kbd {
    font: inherit; font-size: 0.85em; font-weight: 650;
    padding: 0.05rem 0.35rem;
    border: 1px solid var(--line-strong); border-bottom-width: 2px;
    border-radius: 4px; background: #fafbfe;
}
.guide code {
    font-size: 0.9em; padding: 0.05rem 0.25rem;
    background: #fafbfe; border: 1px solid var(--line); border-radius: 4px;
}

.guide__rule { border: 0; border-top: 1px solid var(--line); margin: 1.6rem 0 0.8rem; }

/* On a phone the two-column list stacks, or the value column is four words
   wide and every line wraps. */
@media (max-width: 34rem) {
    .guide .dl { grid-template-columns: 1fr; gap: 0 0; }
    .guide .dl dt { margin-top: 0.5rem; }
}

/* A definition list that stacks on a narrow screen. The two-column .dl is
   right for "Postcode: CV33 9HS" - a short label against a short value - and
   wrong for a paragraph of instructions, which gets an 8rem label column and
   four words a line beside it. Opt-in, so the event and member pages keep the
   shape that suits them. */
@media (max-width: 34rem) {
    .dl--stack               { grid-template-columns: 1fr; gap: 0 0; }
    .dl--stack dt            { margin-top: 0.6rem; font-weight: 650; color: var(--ink); }
}

/* ---------- dark ---------- */

/* One rule does the whole app.
 *
 * A dark mode normally means tokenising every colour and writing a second
 * palette. This does not, and that is deliberate: the audit found 71 colours
 * outside :root, the seven answer colours are chosen by an admin and printed
 * into the page from the database, and the eight dance-skill bands carry hue
 * relationships somebody picked on purpose. A second palette is a second set
 * of decisions to keep in step with the first, forever. Inverting is one
 * decision, and it keeps every relationship the side already chose.
 *
 * WHY IT IS NOT ON html, WHICH WOULD BE SIMPLER.
 *
 * The topbar has to come out of the filter, and counter-inverting it does not
 * work: two passes are not the identity. Measured 5 September 2026 - the
 * side's navy #0018a8, inverted and counter-inverted, comes back a dull slate
 * that is plainly not the same colour beside it. CSS hue-rotate is a
 * non-orthogonal approximation and loses saturation on every pass, so nothing
 * inside a filtered subtree can be made to show a true colour. The only way to
 * keep the brand band exactly as it is in light mode is to never filter it.
 *
 * So the filter goes on body's children instead, and skips .topbar.
 *
 * WHY main IS SKIPPED AND ITS CHILDREN FILTERED INSTEAD.
 *
 * .picker is position: fixed and lives inside main. A filtered element becomes
 * the containing block for its fixed descendants, so filtering main pinned the
 * picker to main's box rather than the window - measured 30px out, sitting
 * above the version line. Filtering main's children one level down leaves the
 * picker with a filter of its OWN, and an element's own filter has no effect
 * on its own positioning. Measured after: picker bottom 828 in an 844 window,
 * which is the 1rem it asks for.
 *
 * The same reasoning is why .install-hint and .live-note are fine: they are
 * direct children of body, so they are filtered individually rather than by an
 * ancestor.
 *
 * data-theme is stamped by assets/theme.js before the first paint. It is
 * always an explicit light or dark - "follow my phone" is resolved there - so
 * there is no prefers-color-scheme block here holding a second copy of these
 * rules.
 */
html[data-theme="dark"],
html[data-theme="dark"] body {
    /* body is not filtered any more, only its children are, so its own ground
       has to be dark outright. Left at the light --bg it shows white through
       every gap between the filtered blocks. */
    background: #14161c;
}

html[data-theme="dark"] body > *:not(.topbar):not(main),
html[data-theme="dark"] main > * {
    filter: invert(1) hue-rotate(180deg);
}

/* The badge in the grid's corner cell sits inside the filtered part of the
   page, and unlike the topbar it cannot be lifted out of it - it is a cell in
   the table. Counter-inverting it does not restore it: two passes are not the
   identity, and the badge's gold sails came back grey.
   So it is not counter-inverted. It is swapped for the image the filter will
   TURN INTO the badge - the pre-image under invert(1) hue-rotate(180deg),
   worked out per pixel by bin/badge.html and written by bin/make-icons.js.
   One pass, and the badge is the same in both themes. */
html[data-theme="dark"] .head__logo {
    content: url("/assets/logo-dark.png");
}

/* THE COLOUR BEING CHOSEN, on Look and on Event types.
 *
 * Approximate, and it cannot be otherwise - see above. Close enough to tell a
 * yellow from a red, with the exact value written beside it as text, which the
 * filter cannot falsify. The native colour popup is drawn by the operating
 * system outside the page and was never inverted, so the place where the
 * choosing actually happens tells the truth anyway.
 *
 * The example beside it, ".setrow__eg", is deliberately NOT counter-inverted:
 * it is labelled "on the grid", and the grid really is inverted, so it is
 * already showing what it says it shows. */
html[data-theme="dark"] .setrow__pick,
html[data-theme="dark"] .typerow input[type="color"] {
    filter: invert(1) hue-rotate(180deg);
}

/* The exact value, for the two pages where approximate is not good enough.
   Hidden in light mode, where the swatch is the truth and this is clutter. */
.swatch-hex { display: none; }

html[data-theme="dark"] .swatch-hex {
    display: block;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.68rem;
    color: var(--ink-soft);
    letter-spacing: -0.02em;
}

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

/* The same shell as the ticksheet's answer picker, and deliberately so: one
   panel, one set of open/close rules, one thing to keep right in dark mode.
   What differs is what it holds - two dropdowns rather than four buttons,
   because a dance is two answers and neither is a one-tap decision. */
.picker--skill { width: min(20rem, calc(100vw - 1.5rem)); }

.picker__pick {
    display: block;
    margin-bottom: 0.55rem;
    font-size: 0.85rem;
    color: var(--ink-soft);
}
.picker__pick select {
    display: block;
    width: 100%;
    margin-top: 0.2rem;
    font: inherit;
    font-size: 0.95rem;
    color: var(--ink);
    padding: 0.4rem;
    border: 1px solid var(--line-strong);
    border-radius: 6px;
    background: var(--paper);
}
.picker__pick select:focus { outline: 2px solid var(--green); outline-offset: -2px; }

/* One button, so the four-column grid the answers use would stretch Close
   across the whole panel. */
.picker__answers--one { grid-template-columns: 1fr; }

/* ---------- how somebody is coming ---------- */

/* Under the answer, in flow: how they are coming, then how likely a maybe is.
   The same two badges as the member page, in the same two colours, so a D on
   the grid and a D beside somebody's name are plainly the same fact.

   In flow rather than tucked in a corner, which is what makes the row taller -
   the cost of saying it properly. Everything is sized against --grid-scale so
   the whole stack shrinks with the zoom; written in fixed rem it would grow to
   fill the cell as the grid got smaller, which is the mistake the dances grid
   already recorded about its own widths. */
/* A FIXED height, so the badge row is at the same place whether or not the
   percentage above it has anything in it. Empty and full are the same size,
   which is the whole point: without it a yes put its D on the second line
   while the maybe beside it put its D on the third. */
.cell__as {
    display: flex;
    justify-content: center;
    gap: calc(0.12rem * var(--grid-scale));
    margin-top: calc(0.12rem * var(--grid-scale));
    line-height: 1;
    /* The badge's own box: the letter, its padding top and bottom, and its two
       1px borders. Written out of the same parts the badge is built from, so
       raising the letter cannot leave the row it sits in behind. */
    height: calc(var(--answer-font) + 0.1rem * var(--grid-scale) + 2px);
}

/* THE SAME SIZE AS THE ANSWER ABOVE IT. It was 0.55rem against the grid scale
   - 7.92px measured at the default zoom, against the answer's 14.69px - which
   is small enough to be a mark rather than a letter, on a sheet a side aged
   twenty-two to eighty reads on a phone. The letter is the label, so it has to
   be legible before the colour of the pill means anything.

   The horizontal padding drops to 0.18rem with it: two pills side by side have
   to fit a member column, which is 2.95rem against the same scale, and the two
   1px borders are the one part of the badge that does NOT shrink with the zoom
   - so the bottom of the zoom range is where it would overflow, and that is
   where it was measured. */
.cell__badge {
    font-style: normal;
    font-size: var(--answer-font);
    font-weight: 700;
    letter-spacing: 0.02em;
    padding: calc(0.05rem * var(--grid-scale)) calc(0.18rem * var(--grid-scale));
    border-radius: 99px;
    border: 1px solid;
}

/* The member page's own two, and deliberately the same hex: this is one fact
   wearing one colour wherever it appears. */
.cell__badge--dan { background: #e7eafa; color: var(--navy-deep); border-color: #c6cef0; }
.cell__badge--mus { background: #fdf6db; color: var(--gold-deep); border-color: #e8d79a; }

/* The size of the badges above it, on its own line, so the three read as one
   stack rather than as a number that has wandered in.

   ALWAYS RENDERED, EVEN EMPTY, and given a fixed height to match: this is the
   middle of three slots, and an empty one that collapsed would pull the badges
   under it up a line. A yes with a D and no percentage has a blank line here,
   which is what puts its D level with the D of the maybe beside it. */
.cell__pct {
    display: block;
    margin-top: calc(0.12rem * var(--grid-scale));
    /* The answer's size, like the badges below it. The height is the same
       value rather than a number that happens to match, because line-height 1
       makes the box exactly the type size and the two must not drift. */
    font-size: var(--answer-font);
    height: var(--answer-font);
    font-weight: 700;
    line-height: 1;
    color: var(--maybe);
}

/* Their own rows under the four answer buttons, not inside that grid: it is a
   fixed four columns, and two toggles or three percentages forced into it
   would leave a hole or put Close beside a percentage. */
.picker__sub {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    margin-top: 0.5rem;
}

/* [hidden] loses to any author rule - the browser hides it from its own
   stylesheet and `display: flex` above beats that whatever the specificity.
   Without this line the attribute sits on the element and the element sits on
   the screen, which is invisible to a grep and obvious in a render. */
.picker__sub[hidden] { display: none; }

/* The note box, always on show. A .picker__pick as well, so it takes that
   row's layout; this only adds the field. It had a [hidden] companion while it
   appeared for one preference alone - gone with the hiding, and worth knowing
   it would be needed again the moment anything hides this row, because
   .picker__pick sets a display and an author rule beats the browser's own
   [hidden] stylesheet. */
.picker__detail input {
    width: 100%;
    margin-top: 0.25rem;
    padding: 0.4rem 0.5rem;
    font: inherit;
    color: var(--ink);
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: 6px;
}

.picker__detail input:focus { outline: 2px solid var(--green); outline-offset: -2px; }

.picker__sub-label {
    flex: none;
    font-size: 0.8rem;
    color: var(--ink-soft);
    min-width: 4.4rem;
}

.sub {
    flex: 1;
    font: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    padding: 0.4rem 0.2rem;
    border: 1px solid var(--line-strong);
    border-radius: 6px;
    background: var(--paper);
    color: var(--ink);
    cursor: pointer;
}
.sub:hover { border-color: var(--green); }

/* The on-state takes the colour of the answer it belongs to, so the sub-row
   reads as the tail of the button above it rather than as its own thing. */
#subCapacity .sub[aria-pressed="true"] { background: var(--yes);   color: #fff; border-color: var(--yes); }
#subChance   .sub[aria-pressed="true"] { background: var(--maybe); color: #fff; border-color: var(--maybe); }

/* The sub-options on the page an email link lands on. A fieldset rather than
   the picker's toggle buttons: this is a plain form with no JavaScript at all,
   which is what makes it work from any mail client on any device. */
.confirm__more {
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 0.5rem 0.8rem 0.7rem;
    margin: 0 0 0.9rem;
}
.confirm__more legend { font-size: 0.85rem; color: var(--ink-soft); padding: 0 0.3rem; }
.confirm__more .check { margin-top: 0.25rem; }

/* ==========================================================================
   Meetings and the shelf
   ========================================================================== */

/* The title and its one button on a line, dropping to two lines when the
   button will not fit beside it. Inside .panel, which is the wrapper every
   other non-grid page uses - these three were written against a .page of my
   own invention, which existed nowhere and left the content flush against
   the left edge of the window. */
.panel__head {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem 1rem;
    align-items: center;
    justify-content: space-between;
}

.panel__head h1 { margin: 0; }

/* --- searching the minutes --- */
.search {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
    margin: 1rem 0 1.25rem;
}

.search__label { flex: 0 0 100%; font-weight: 600; font-size: 0.9rem; }

.search__box {
    flex: 1 1 16rem;
    min-width: 0;
    padding: 0.5rem 0.6rem;
    font: inherit;
    color: var(--ink);
    background: var(--paper);
    /* --line-strong, the same as every other input in the app. It was --line,
       which is the colour of a separator between rows and is too pale at the
       edge of a box: the search box read as a stretch of white until you put
       the cursor in it and the focus ring drew the only edge it ever had. */
    border: 1px solid var(--line-strong);
    border-radius: 6px;
}

.search__box:focus { outline: 2px solid var(--green); outline-offset: -2px; }

/* --- the list of meetings --- */
.meets { margin: 0; padding: 0; list-style: none; }

.meets__item {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.3rem 0.75rem;
    padding: 0.7rem 0;
    border-bottom: 1px solid var(--line);
}

.meets__link { flex: 1 1 14rem; text-decoration: none; }
.meets__title { display: block; font-weight: 650; font-size: 1.05rem; }
.meets__when  { display: block; color: var(--ink-soft); font-size: 0.88rem; }
.meets__meta  { color: var(--ink-soft); font-size: 0.85rem; }

/* Draft or sent, and sent is the one that matters: it is also what says the
   minutes can no longer be edited. Built like the grid's other stamps. */
.meets__sent,
.meets__draft {
    display: inline-block;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    padding: 0.15em 0.5em;
    border-radius: 3px;
    white-space: nowrap;
}

.meets__sent  { background: var(--green); color: #fff; }
.meets__draft { background: transparent; color: var(--ink-soft); border: 1px solid var(--line); }

/* --- one set of minutes, read --- */
.mins { margin: 1.25rem 0; }

.mins__head {
    margin: 0 0 0.4rem;
    font-size: 1.05rem;
    padding-bottom: 0.25rem;
    border-bottom: 2px solid var(--line);
}

.mins__body { line-height: 1.55; }

/* --- writing one up --- */
.sect {
    margin: 0 0 1.1rem;
    padding: 0.75rem 0.9rem 0.3rem;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--paper);
}

.sect__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-bottom: 0.4rem;
}

.sect__n {
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

/* The last section keeps its box but loses its Remove: a form with nothing to
   type in is not a form. The arrows go the same way at the ends of the run.
   All hidden from the script, so they need the companion rule - an author
   display beats the browser's own [hidden]. */
.sect__drop[hidden],
.sect__move[hidden] { display: none; }

/* The buttons that act on this section, together at the right of its bar, so
   the two arrows read as a pair and Remove sits apart from them. */
.sect__moves {
    display: flex;
    align-items: center;
    gap: 0.15rem;
}

/* Square, because an arrow is a glyph and not a word - wide enough to be a
   comfortable target on a phone without the pair becoming a wall.

   THE ARROWHEAD IS SET LARGER THAN THE BUTTON'S WORDS WOULD BE. It inherited
   the button's font size, 13.6px, which is the size of the body text beside
   it - so a control whose whole job is to be recognised at a glance was drawn
   at the size of a letter. A word can afford that because it is read; a glyph
   has to be seen. Measured before and after rather than nudged. */
.sect__move {
    min-width: 1.9rem;
    text-align: center;
    padding-left: 0.3rem;
    padding-right: 0.3rem;
    font-size: 1.15rem;
    line-height: 1;
}

.sect__moves .sect__drop { margin-left: 0.35rem; }

.sect__head { font-weight: 650; }
.sect__tools { margin: 0 0 0.5rem; }

/* --- picking a dance out to link --- */
.picker--events { max-width: 26rem; }

.picker__events {
    margin: 0.5rem 0;
    padding: 0;
    list-style: none;
    max-height: 45vh;
    overflow-y: auto;
}

.picker__event {
    display: block;
    width: 100%;
    text-align: left;
    padding: 0.5rem 0.6rem;
    font: inherit;
    color: var(--ink);
    background: transparent;
    border: 0;
    border-bottom: 1px solid var(--line);
    cursor: pointer;
}

.picker__event:hover,
.picker__event:focus-visible { background: var(--green-soft); }

.picker__event-title { display: block; font-weight: 650; }
.picker__event-when  { display: block; color: var(--ink-soft); font-size: 0.85rem; }

/* --- what came with the minutes --- */
/* One list under the sections, however many sections attached to it: a file
   goes out with the email and is read back here, so which box somebody was
   typing in when they chose it is not a fact worth showing. */
.files { margin: 1.25rem 0; }

.files__head {
    margin: 0 0 0.3rem;
    font-size: 1rem;
}

.files__list {
    margin: 0.5rem 0;
    padding: 0;
    list-style: none;
}

.files__one {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.5rem 0.75rem;
    padding: 0.45rem 0;
    border-bottom: 1px solid var(--line);
}

/* The row is built hidden and shown once a file is actually chosen. An author
   `display` beats the browser's own [hidden], so it needs saying twice - the
   same trap .sect__drop carries above. */
.files__one[hidden] { display: none; }

/* Not yet on the server: it goes up with the Save, like everything else on
   this form. Marked so it does not read as one that is already kept. */
.files__one--new { border-left: 3px solid var(--green); padding-left: 0.6rem; }

.files__name { font-weight: 650; }

/* --- sending them out --- */
/* Its own bordered block: the one button on the page that cannot be undone,
   because it puts the minutes in twenty-two inboxes and fixes them where they
   stand. It should not sit in the flow looking like Save. */
.sendout {
    margin: 1.5rem 0;
    padding: 0.9rem 1rem 0.4rem;
    border: 1px solid var(--line);
    border-left: 4px solid var(--green);
    border-radius: 8px;
    background: var(--paper);
}

.sendout__head { margin: 0 0 0.3rem; font-size: 1rem; }

/* --- the shelf --- */
.upload {
    margin: 1rem 0 1.5rem;
    padding: 0.9rem 1rem 0.4rem;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--paper);
}

.upload__head { margin: 0 0 0.5rem; font-size: 1rem; }
.upload__note { font-size: 0.85rem; }

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

.shelf__item {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.3rem 0.75rem;
    padding: 0.7rem 0;
    border-bottom: 1px solid var(--line);
}

.shelf__title { flex: 1 1 14rem; font-weight: 650; }
.shelf__meta  { color: var(--ink-soft); font-size: 0.85rem; }
.shelf__drop  { margin: 0; }

/* A removed document keeps its row - that IS the history of deletions - and
   loses its link, because the file behind it is gone. Greyed rather than
   faded, so every word of it stays readable. */
.shelf__item--gone .shelf__title { color: var(--ink-soft); font-weight: 500; text-decoration: line-through; }
.shelf__gone { color: var(--ink-soft); font-size: 0.85rem; font-style: italic; }

.empty { color: var(--ink-soft); padding: 1rem 0; }
.notice--bad  { border-left: 4px solid rgb(var(--needs)); }
.notice--warn { border-left: 4px solid var(--maybe); }

.form__row { margin: 0.9rem 0; }
.form__row--save { display: flex; gap: 0.5rem; align-items: center; }
.danger { margin: 1.5rem 0 0; }

/* ---------- the dance list, the third grid ----------
   Dances down the side, whoever is coming across the top, a letter in each
   cell. It inherits the whole grid skeleton - zoom, frozen panes, the pinned
   left column, the flex-column scroller - and only says what differs.

   The cells are .slot and NOT .cell, deliberately: app.js matches
   `.cell:not(.skill)` for the ticksheet's answer picker, so a .cell here would
   open a Yes/No/Possible panel on a dance list and then throw, because #picker
   does not exist on this page. Everything below therefore has to state what
   .cell would otherwise have given it. */
.grid--dancelist {
    /* Wider than either other grid's left column, because it carries the row
       number, the dance, its tradition and type, and four controls. Against
       --grid-scale, or zooming out would leave it a constant while everything
       else shrank. */
    --ev-w:  calc(17rem   * var(--grid-scale));
    --you-w: calc(2.95rem * var(--grid-scale));
    --tot-w: calc(2.6rem  * var(--grid-scale));

    /* THE SIZE A ROW IS NAMED AT, and one value because two things name a row:
       a dance and a heading. They were 13.87px and 12.38px, which reads as two
       kinds of row rather than as two rows. The number is what `.ev__line` and
       the base `.ev__title { font-size: 1.12em }` were multiplying out to, so
       nothing moves on the dance rows - it is only written down now, where the
       heading can be told to use the same one. */
    --rowname: calc(0.9632rem * var(--grid-scale));
}

/* A FIFTH LINK IN THE FLEX CHAIN, and it has to flex like the other four.
   The chain that gives the table a bounded height is body.is-grid -> main ->
   .gridwrap -> .gridscroll, and this page puts a <form> between the last two
   so the rows and the title can be saved together. A plain block there breaks
   every link below it, and the failure is four things at once, none of which
   names the cause:
     - .gridscroll's `flex: 1 1 8rem` is ignored, so it grows to its content:
       measured 953px inside an 844px viewport
     - the page scrolls instead of the table
     - the names row never freezes, because a sticky element sticks inside its
       nearest scrolling ancestor and that ancestor is no longer scrolling
     - and `overscroll-behavior: contain` on the scroller then swallows the
       wheel, so the page feels stuck as well
   Anything else ever wrapped around a grid needs these four lines. */
.gridwrap__form {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

/* THIS GRID HAS THINGS UNDER IT, WHICH THE OTHER TWO DO NOT. The add-a-dance
   forms, the legend and the history all follow it inside `main`, so `main`
   hands the wrapper only what is left after them - which is its floor, 8rem,
   a window four rows deep. The other two grids get the whole viewport because
   nothing follows them.
   So this one asks for a height of its own rather than whatever remains: deep
   enough to work in, shallow enough that the page still scrolls to what is
   below, and in a viewport unit so it follows the phone rather than a number
   somebody guessed. Measured at 390x844 and 1000x800. It is `svh` and not
   `dvh` - see the rule itself; a cap that follows the viewport perfectly also
   follows it changing mid-scroll, which is a layout moving under the paint. */
/* A CAP, NOT A FLOOR, AND IT DOES NOT GROW. This was `min-height`, and the
   effect was that "What the letters mean" sat at the same place near the
   bottom of the window however few rows the sheet had - the scroller held
   62svh of empty paper and the panel below it looked frozen there rather than
   following the grid.

   `flex: 0 1 auto` is the other half: `.gridscroll` is `flex: 1 1 8rem` for the
   two grids that fill the window, and a growing flex item ignores its content
   height. Nothing under it on those pages, so nothing to push down; this one
   has the legend and the history, so it takes what it needs and stops. The cap
   is what stops fifty dances filling the page instead of scrolling.

   `min-height: 0` because the shared rule's basis is a height in a column and
   would otherwise be a floor by another name. */
.grid--dancelist-scroll {
    flex: 0 1 auto;
    min-height: 0;
    /* THE CAP IS "AS MUCH AS IS LEFT AFTER THE KEY", AND IT IS MEASURED.
       62svh was a guess that came out two ways at once: on a phone it left
       the sheet four rows short of the window with the whole panel on screen
       under it, and on a monitor it stopped the sheet less than half way down
       and parked the panel at the bottom of a field of grey.

       What is wanted is one line: the key sits at the foot of the window and
       the history starts below it, so a sheet too tall to fit gets every
       pixel that is not the key - and a sheet that does fit is not stretched
       to the window, because this is a cap and the basis is the content.

       --sheet-cap is set by sheet-foot.js as `calc(100svh - Npx)`, where N is
       everything above the scroller plus the height of the key band under it,
       both measured. Two things that are deliberate: the arithmetic is a
       calc() so the VIEWPORT half stays in CSS - svh, so it does not follow
       Chrome for Android hiding its URL bar the way a number read out of
       innerHeight would - and N is height-independent, so a resize recomputes
       the same number rather than moving the layout mid-scroll.

       The fallback is the old cap, for no-JS: two thirds of the window, so
       there is still something of the page below the sheet to say it has
       ended. vh before svh, or a browser that has never heard of svh drops
       the only declaration and the grid is uncapped. */
    max-height: 62vh;
    max-height: var(--sheet-cap, 62svh);
}

/* AND THE WRAPPER DOES NOT GROW EITHER, which is the other half of the same
   fault and the one that made the grey. `.gridwrap` is `flex: 1 1 auto` for
   the two grids that own the window; here it grew to whatever was left after
   the panel and held the surplus as empty paper BELOW the capped scroller -
   measured at 390x1500: scroller ending at 1053, wrapper at 1283, the panel
   pushed down to 1299. So the sheet stopped and the key was 246px away from
   it, at the bottom of the window, with nothing in between.
   Content-sized and shrinkable, so the panel follows the last row. */
.gridwrap--foot { flex: 0 1 auto; }

/* One member column each, the ticksheet's width: a letter is all that goes in
   one, and twenty of them have to fit. */
.grid--dancelist .slot {
    width: var(--you-w);
    padding: 0.1rem;
    text-align: center;
    vertical-align: middle;
    position: relative;
    /* The vertical rules that separate the dancers, which every one of the
       side's own sheets has. A hairline, because twenty of them at any more
       would read as a fence. */
    border-right: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
    cursor: pointer;
}
.grid--dancelist .slot__v {
    display: block;
    height: calc(1.35rem * var(--grid-scale));
    line-height: calc(1.35rem * var(--grid-scale));
    font-size: calc(0.82rem * var(--grid-scale));
    font-weight: 600;
}
.grid--dancelist .slot--you { position: sticky; z-index: 10; left: var(--pin-you, var(--ev-w)); }

/* THE MARKS ARE QUIET. A pale wash each, and the letter carries the meaning -
   the published sheets do this with bold and nothing else, so anything louder
   would be shouting.

   Painted as a background-image gradient rather than a background-color,
   because `.grid tbody tr.row--alt td:not(.cell--you):not(.tot)` is three
   elements and four classes and outranks anything a single class can say. The
   dance skill colours learnt this the hard way; the note is in CLAUDE.md. */
.grid--dancelist .slot--caller        { background-image: linear-gradient(#dbe7fb, #dbe7fb); }
.grid--dancelist .slot--lead_musician { background-image: linear-gradient(#d9efe2, #d9efe2); }
.grid--dancelist .slot--musician      { background-image: linear-gradient(#eaf6ef, #eaf6ef); }
.grid--dancelist .slot--reserve       { background-image: linear-gradient(#fbf0d8, #fbf0d8); }
.grid--dancelist .slot--dancer        { background-image: linear-gradient(#f2f3f7, #f2f3f7); }

/* A stronger wash of the SAME colour where somebody has chosen it by hand,
   which is both "this was a decision" and "the auto-picker will not touch it".
   Same hue, more of it - a different colour would read as a different kind of
   mark. */
.grid--dancelist .slot--fixed.slot--caller        { background-image: linear-gradient(#b9d1f6, #b9d1f6); }
.grid--dancelist .slot--fixed.slot--lead_musician { background-image: linear-gradient(#b4e0c8, #b4e0c8); }
.grid--dancelist .slot--fixed.slot--musician      { background-image: linear-gradient(#d3ecdd, #d3ecdd); }
.grid--dancelist .slot--fixed.slot--reserve       { background-image: linear-gradient(#f6e2b0, #f6e2b0); }
.grid--dancelist .slot--fixed.slot--dancer        { background-image: linear-gradient(#e2e5ee, #e2e5ee); }

/* The totals and the check columns run off the end, unpinned, for the reason
   the dances grid gives: "does this row add up" is read across the row. Both
   halves of the pinning have to be undone or a static cell is pushed sideways
   by the offset it kept. */
/* THE COLUMN IS UNPINNED AND THE HEADER IS NOT, which is two decisions and
   used to be one. `position: static` was set on both to undo the shared rule
   that pins the totals column to the LEFT - right for the events grid, where
   the count is read beside your own answer, and wrong here, where "does this
   row add up" is read across the row. But static took the header out of the
   sticky header row as well, so scrolling the sheet down carried the word
   Totals away while every member's name stayed put: measured at a scrollTop of
   200, the names sat 1px from the top of the scroller and Totals was 199px
   above it and gone.

   So the body cells are static and the header keeps the `position: sticky;
   top: 0` that `.grid .head` gives every other header cell - only its `left`
   is given back, which is the half that was actually meant. */
.grid--dancelist .tot {
    position: static;
    left: auto;
}
.grid--dancelist .head--tot { left: auto; }
.grid--dancelist .tot,
.grid--dancelist .head--tot {
    width: var(--tot-w);
    border-right: 1px solid var(--line);
}
/* Centred in their columns. They are single figures in narrow columns, and
   left-aligned they drifted away from the headings above them.

   JUSTIFY-CONTENT AND NOT TEXT-ALIGN. `.tot span { display: flex }` is set for
   the events grid, so the figure is an anonymous flex item and text-align has
   nothing to say about where it goes - it sat at flex-start whatever the cell
   declared. The first attempt set text-align on the cell and CHECKED IT BY
   READING getComputedStyle().textAlign, which duly said "center" and proved
   only that the property had been set. The number had not moved. Measured
   properly the second time, with a Range around the text node. */
.grid--dancelist .tot     { text-align: center; }
.grid--dancelist .tot__n  {
    justify-content: center;
    font-size: calc(0.8rem * var(--grid-scale));
    font-weight: 600;
}

/* Green when the row has the dancers it wants, amber when it does not. The one
   loud thing on the grid, because a row one short is otherwise invisible - and
   it is the reason the side's own sheets carry a Difference column at all. */
.grid--dancelist .tot--right .tot__n { color: #1c6b43; }
.grid--dancelist .tot--short .tot__n { color: #8a5a00; }
.grid--dancelist .tot--short         { background-image: linear-gradient(#fdf3e2, #fdf3e2); }

/* The left column: number, name and the two codes on ONE line at ONE size,
   then the controls under them.

   All three were different sizes and two were on a second line, so the eye had
   three things to do to read one row. They are now the same size as each other:
   the number is distinguished by weight and colour rather than by being
   smaller, because it is a position and not a smaller kind of name, and the
   codes are grey for the same reason. */
.grid--dancelist .ev      { vertical-align: middle; }
/* NOWRAP, AND THE NAME IS THE ONLY THING THAT GIVES. This wrapped, which reads
   as the right answer and is not: flex collects items onto a line at their
   own content widths and only then shrinks them, so a name too long for the
   column took the whole of itself to a second line and sent the number and the
   codes to lines of their own. Measured at 360px, where the column is 158px:
   Bonny Green Garters was three lines, 56px - the number alone, then the name,
   then the codes - and Hereburgh Processional four, at 72px. Ten rows of a
   twelve-row sheet were taller than they needed to be, and the number had come
   away from the name it belongs to.
   So the line never breaks and the NAME wraps inside its own box instead: the
   number stays at the left of the first line and the codes at the right of it,
   which is what they are there for. See `.ev__title` and `.ev__codes` below
   for the two halves of that. */
.grid--dancelist .ev__line {
    display: flex;
    align-items: baseline;
    flex-wrap: nowrap;
    gap: 0.25rem;
    font-size: calc(0.86rem * var(--grid-scale));
}
/* Close to the name, because the two are read as one thing - "row four,
   Balancy Straw". The number keeps a tabular width so a two-digit row does
   not shunt every name along, but no more room than that. */
.grid--dancelist .ev__n {
    flex: none;
    min-width: calc(0.95rem * var(--grid-scale));
    margin-right: -0.1rem;
    font-weight: 400;
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
}
/* Said as the variable rather than left to the base rule's 1.12em, which
   computes to the same pixel: the heading is set from the same value, and two
   things that must match should not match by arithmetic somebody has to
   redo. */
/* THE ONE THING ON THE LINE THAT SHRINKS, and `min-width: 0` is what allows
   it: a flex item will not go below the width of its longest word without
   that, which on this column is most of the room there is. With it the name
   takes what is left after the number and the codes and wraps inside itself,
   so a two-line name is two lines of name rather than three lines of row.
   `overflow-wrap` is the last resort and only fires where a single word is
   wider than what is left - it breaks the word instead of spilling it over
   the next column, which on a sticky pinned cell would sit on top of the
   first dancer. */
.grid--dancelist .ev__title    { font-size: var(--rowname); font-weight: 650;
                                 flex: 0 1 auto; min-width: 0;
                                 overflow-wrap: break-word; }
/* The codes at the right-hand end of the line rather than trailing the name:
   they are a fact about the dance and not part of what it is called, and on
   the right they line up down the sheet instead of starting wherever the name
   happens to end.
   `flex: none` says the same thing the number says: the name is what gives.
   `padding-left` and not the line's own `gap`, because `margin-left: auto`
   collapses to nothing the moment the name fills its share: at 360px
   Bromsberrow ended 4px from Bor and the two read as one run of words. A
   padding is separation the name cannot take back, and it costs the name 5.6px
   rather than widening the gap under the number as well. */
.grid--dancelist .ev__codes {
    margin-left: auto;
    padding-left: 0.35rem;
    flex: none;
    display: inline-flex;
    gap: 0.3rem;
    color: var(--ink-soft);
}
.grid--dancelist .ev__code--off { font-style: italic; }
/* The controls block sits under the name in this cell, and is .listrow__lead
   like every other row's - see there. */
.grid--dancelist .ev .listrow__lead { margin-top: 0.2rem; }
/* The two numbers, stacked and each labelled - see .ev__nums below. */

/* Whoever plays, marked above their name the way the side's sheets mark it in
   a row of their own - which this grid cannot have, because pinColumns()
   measures the first thead row.

   ALWAYS THE SAME HEIGHT, WHETHER OR NOT THERE IS A LETTER IN IT. The template
   renders the slot on every column for exactly this: an empty span would
   collapse and the name below it would start higher than its neighbour's, so
   twenty rotated names would arrive at twenty heights. The rule beneath it is
   the separation - the M was sitting against the first letter of the name. */
.grid--dancelist .head--you,
.grid--dancelist .head--mem {
    vertical-align: bottom;
    /* Reserves the strip the mark sits in, since the mark itself is taken out
       of the flow below. */
    padding-top: calc(1.25rem * var(--grid-scale));
}

/* OUT OF THE FLOW, PINNED TO THE TOP OF THE CELL. In the flow it sat directly
   above the rotated name - and a rotated name's box is as tall as the name is
   long, so the mark got pushed up by a different amount in every column and
   twenty of them arrived at twenty heights. Absolute against the header cell,
   which is already `position: sticky` and so already a containing block, puts
   every mark on one line and leaves every name bottom-aligned. Measured both
   ways: one distinct name bottom, one distinct mark bottom. */
.grid--dancelist .head__plays {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: calc(1.1rem * var(--grid-scale));
    line-height: calc(1.1rem * var(--grid-scale));
    text-align: center;
    font-size: calc(0.66rem * var(--grid-scale));
    font-weight: 700;
    color: #1c6b43;
    /* The separation. The mark was against the first letter of the name. */
    border-bottom: 1px solid var(--line);
}

/* A HEADING, A PLACE, OR A GAP - and they have to LOOK like three things.
   The first go set a background and a font on the cell, which failed twice
   over: the two greys were a shade apart, and in edit mode the words are
   inside an <input>, which does not inherit font-weight or font-style from its
   cell. So the difference is stated on the input itself, and the row carries a
   word saying which it is - styling alone was asking the reader to learn a
   convention that two authors of the side's own sheets did not share. */
.listrow__cell { padding: 0.3rem 0.5rem; }

.listrow--band .listrow__cell {
    /* A heading is a band across the sheet, like the years on the events grid
       and "No longer in the repertoire" on the dances one. */
    background: #e6e9f0;
    border-top: 1px solid var(--line-strong);
    border-bottom: 1px solid var(--line-strong);
}
/* THE COLOUR IS NOT DECORATION HERE. `.yearline__label` is shared with the
   events grid's year band, which is white type on the side's navy - so on this
   grid's pale grey band a heading was white on #e6e9f0 and could not be read
   at all. Only in the READING view, which is why it went unseen: the builder
   sees a text box with the words in it, and the label is what a member gets.
   A place was fine by luck, because `.listrow--comment` sets a colour of its
   own two rules below. Found in a render of member mode. */
/* A HEADING IS SET EXACTLY AS A DANCE NAME IS, and it was not: `.ev__line` is
   0.86rem against the grid scale at weight 650, where a heading came out at
   the table's own size and weight 700 - and in the BOX it came out in Arial at
   13.33px, because a form control inherits neither family nor size from the
   page unless it is told to. Three differences between two things that name
   rows on the same sheet. The base rule below now inherits the family, and
   this says the size and the weight in the same numbers `.ev__title` uses.
   The word "Heading" beside it follows, since it labels that text. */
.listrow--band .listrow__text,
.listrow--band .yearline__label,
.listrow--band .listrow__kind {
    font-size: var(--rowname);
    font-weight: 650;
    color: var(--ink);
}

.listrow--comment .listrow__cell {
    /* A place is a note between two dances, not a division of the list, so it
       is indented under them rather than banded across. */
    background: #fbfcfe;
    padding-left: 1.6rem;
}
/* A place is quieter than a heading and stays so - but the two views of it have
   to agree, and they did not: `.yearline__label` is the events grid's year
   band, weight 700 at 0.95em, so a place READ was bolder and smaller than the
   same place being EDITED. The size and weight are said here for that reason;
   the italic grey is what makes it a note either way. */
.listrow--comment .listrow__text,
.listrow--comment .yearline__label {
    font-style: italic;
    font-size: inherit;
    font-weight: 400;
    color: var(--ink-soft);
}

.listrow--blank .listrow__cell { height: calc(0.8rem * var(--grid-scale)); }

/* Which of the two it is, said in a word - and set like the words it labels
   rather than as a chip in a voice of its own. It was uppercase, small and
   letter-spaced, which made it a third kind of text on a row that already had
   two. Only its width is fixed, because the boxes after it have to start in
   the same place either way.

   "It inherits from the row instead, so a heading's word is bold and a place's
   is italic grey" is what this comment used to claim, and it was not so: the
   rules that make a heading bold name `.listrow__text` and `.yearline__label`
   and never this, so the word inherited the row's ordinary weight while the
   box beside it was bold - and the grey below made it quieter still. It is
   said outright now, once per row kind, so the label really does match the
   thing it labels. */
.listrow__kind {
    display: inline-block;
    flex: none;
    width: 3.4rem;
    color: var(--ink-soft);
}
/* The band's own kind label is set with the heading text further up, where the
   size and the weight are said together. A second rule for it here is how it
   came to be weight 700 against the heading's 650 - two rules at the same
   specificity, and the later one winning without either being wrong. */
.listrow--comment .listrow__kind { font-style: italic; color: var(--ink-soft); }

/* THE HELPER COLUMNS AND ROWS ARE NOT THE SHEET. Everything to the right of
   the last dancer, and everything below the last dance, is scaffolding for
   whoever is building it - so it is fenced off rather than left to read as
   more grid. The side's own published sheet has exactly this rule after Set.

   `.slot + .tot` is the first totals cell in a row, whichever it is, and
   `.head--mem + .head--tot` its header - so neither needs a class of its own,
   and neither can drift when a column is added. */
.grid--dancelist .slot + .tot,
.grid--dancelist .listrow__cell + .tot,
.grid--dancelist .head--mem + .head--tot,
.grid--dancelist .head--you + .head--tot { border-left: 2px solid var(--ink-soft); }

/* NO HEAVY LINE BETWEEN THE DANCE AND THE DANCERS. There was a 2px --ink-soft
   border down the right of the name column, put there by the same reasoning as
   the fence above: the pinned block is one thing and the dancers are another.
   It is not the same case. The fence to the right earns its weight because
   what is beyond it is arithmetic nobody reads across - a heading running into
   it would put a place name inside a sum. Here the whole point is to read
   ACROSS: this dance, then who is in it. A dark rule down the middle of that
   is a wall through the one movement the sheet is for, and the side said so.
   The hairline every other cell carries is what separates them now. */

/* And the counts along the bottom, fenced off from the last dance. */
.grid--dancelist .listrow:not(.listrow--tally) + .listrow--tally > * { border-top: 2px solid var(--ink-soft); }

/* Right-justified against the first member column, because they label the row
   of numbers to their right rather than heading a column of their own - the
   same reason a total sits under what it totals. `.grid .ev` sets left, so the
   override has to name the row. */
.grid--dancelist .listrow--tally .ev { text-align: right; }
.listrow--tally .ev__title        { font-style: italic; color: var(--ink-soft); }
.listrow--tally .slot             { cursor: default; }
.listrow--tally .slot__v          { font-weight: 400; color: var(--ink-soft); }

/* EVERY ROW'S CONTROLS IN ONE BLOCK, THE WIDTH OF THE NAME COLUMN. The arrows
   at its left and the bin at its right, so both sit in a column down the sheet
   whatever kind of row it is - a dance, a heading or a place. Laid out per row
   type they landed at three different x positions, which is what made the
   thing look untidy however tidy each row was on its own.

   The width is --ev-w less the cell padding either side, because the pinned
   name column and the full-width heading rows have to arrive at the same
   inner width from two different cells. */
.listrow__lead {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    flex: 0 0 calc(var(--ev-w) - 1.1rem);
    width: calc(var(--ev-w) - 1.1rem);
    box-sizing: border-box;
}
.listrow__mid   { display: inline-flex; align-items: center; gap: 0.25rem; min-width: 0; }
.listrow__moves { display: inline-flex; gap: 0.2rem; flex: none; }

/* The heading and place rows put the block and the name box side by side.
   THE FLEX GOES ON A WRAPPER INSIDE THE CELL, NEVER ON THE CELL. A <td> with
   `display: flex` stops being a table cell, and a cell that is not a table
   cell does not honour `colspan` - so the row sized itself to its own content
   and the name box stopped 600px short of the table's right edge, on a row
   whose whole job is to span it. Measured: cell 438px inside a table 1050px
   wide, with colspan="16" set correctly all along. */
/* --row-inset is one number used twice, and it has to be: the words on these
   rows are `position: sticky; left: 0` (see `.yearline__label`, where the
   events grid's year band starts hard against a cell with no padding at all
   and 0 is exactly right), while here the cell HAS padding - so a heading and
   a place sat 8.8px in at rest and snapped to the scroller's edge on the first
   pixel of a sideways pan. Reported as the header and the place text moving
   slightly while panning; measured at scrollLeft 0, 20 and 200 as 8.8, 0, 0.
   The sticky offset is now the same custom property as the padding, so the two
   cannot drift apart and the words do not move at all. */
.listrow--band .listrow__cell,
.listrow--comment .listrow__cell,
.listrow--blank .listrow__cell { --row-inset: 0.55rem;
                                 padding: 0.3rem var(--row-inset); }
.grid--dancelist .listrow .yearline__label { left: var(--row-inset, 0); }

.listrow__inner {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
}

/* THE ARROWS KEEP THEIR SPACE AT THE ENDS OF THE RUN. The meeting sections
   collapse theirs, which is right on a bar that holds one thing - here it
   would shift the whole column of arrows sideways on the first and last rows
   of the list, which is exactly the raggedness being fixed. Invisible and
   unclickable, but still occupying its square. */
.grid--dancelist .sect__move[hidden] {
    display: inline-block;
    visibility: hidden;
}

/* A bin, grey until it is going to be used and red once it is. It was a bare
   checkbox with a times sign beside it, which read as neither a control nor a
   state. The checkbox is still the control - this is a checkbox, ticked now
   and acted on when the list is saved, so that Cancel cancels a removal too -
   and the icon is what it looks like. */
.listrow__bin {
    margin: 0 0 0 auto;
    flex: none;
    display: inline-flex;
    align-items: center;
    padding: 0.2rem;
    border-radius: 5px;
    cursor: pointer;
}
.listrow__bin .bin {
    width: calc(1.05rem * var(--grid-scale, 1));
    height: calc(1.05rem * var(--grid-scale, 1));
    fill: none;
    stroke: currentColor;
    stroke-width: 1.3;
    stroke-linecap: round;
    color: var(--ink-soft);
}
.listrow__bin:hover .bin { color: #a3231c; }

/* Ticked: red and heavier, so it reads across a sheet of twenty columns. Done
   with a sibling selector and not :has(), which older iOS Safari does not
   know - and a rule it does not know is a rule it drops in silence, which is
   the shape of fault this project keeps finding. */
.listrow__bin input:checked ~ .bin { color: #c0271f; stroke-width: 1.9; }

/* Keyboard focus has to land on something visible, since the box is clipped. */
.listrow__bin input:focus-visible ~ .bin { outline: 2px solid var(--navy-mid); outline-offset: 2px; }

/* font-family and font-size inherited, which a text input does NOT do on its
   own: both boxes on this sheet were rendering in Arial at the browser's own
   13.33px, in the middle of a page set in system-ui at the grid's size.
   Measured, not spotted - the two faces are close enough at that size to look
   like a weight difference. */
.listrow__text {
    flex: 1 1 8rem;
    min-width: 0;
    font-family: inherit;
    font-size: inherit;
}

/* THE TWO NUMBERS, STACKED AND LABELLED. Dancers over Sets, each with its word
   beside its box. Side by side and abbreviated they read as one expression -
   "Set 2 x 3 sets" - which is not what they are: one is how many people are in
   the set, the other is how many sets are up at once. Two labelled boxes on
   two lines cannot be misread as arithmetic.

   No flex-basis on either row of the stack: in a column a basis is a HEIGHT,
   which this stylesheet has been caught by before. */
.grid--dancelist .ev__nums {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;
    /* Clear of the down arrow. The stack butted straight up against it, so the
       word and the button read as one control. */
    margin-left: 0.45rem;
}
.grid--dancelist .ev__num {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    margin: 0;
}
/* Right-justified, so both words end against their boxes and the two boxes
   read as a column rather than as two labels of different lengths. */
.grid--dancelist .ev__num > span {
    width: calc(3rem * var(--grid-scale));
    text-align: right;
    font-size: calc(0.68rem * var(--grid-scale));
    color: var(--ink-soft);
}
.grid--dancelist .ev__num input {
    width: calc(2.7rem * var(--grid-scale));
    padding: 0.1rem 0.2rem;
    text-align: center;
}

.listbar__title { min-width: min(16rem, 45vw); font-weight: 600; }
.field--inline  { margin: 0; }
.field--narrow  { max-width: 6rem; }
.listadd        { display: flex; gap: 0.5rem; align-items: flex-end; flex-wrap: wrap; margin: 0.6rem 0 0; }
.listadd .field { margin: 0; }

/* The cell panel, reusing the .picker shell for position, size and dark mode.
   One row per mark, because a letter alone is a thing to be taught: the word
   is beside it. */
.picker--slot { width: min(20rem, calc(100vw - 1.5rem)); }
.picker__answers--slot { grid-template-columns: 1fr; gap: 0.25rem; }
.ans--slot { display: flex; align-items: center; gap: 0.6rem; justify-content: flex-start; }
.ans__what { font-weight: 400; font-size: 0.85rem; color: var(--ink-soft); }

@media (max-width: 40rem) {
    /* The same reasoning as the other two grids: a wide left column is over
       half a phone screen, and the names do not need it. Measured at 390px. */
    .grid--dancelist { --ev-w: calc(11rem * var(--grid-scale)); }

    /* At that width the name column cannot hold two arrows, two numbers and a
       bin on one line: the bin has margin-left:auto, so it collapsed straight
       on top of the sets box. The numbers drop to a line of their own and the
       arrows and the bin keep theirs - which is what keeps both of them in the
       column they were just put in. Only on a dance row: a heading's middle is
       one short word and has always fitted. */
    .grid--dancelist .ev .listrow__lead { flex-wrap: wrap; }
    .grid--dancelist .ev .listrow__mid  { order: 3; flex: 1 0 100%; margin-top: 0.2rem; }
}

/* A select holding "Wellesbourne Beaver Scouts - Wed 16 Sep 2026" is wider than
   a phone, and a flex item will not shrink below its content without being
   told to. The document did not scroll, so this is not the width trap in the
   notes - it is the box running to the edge of the panel and looking clipped. */
.listadd .field,
.listadd select { min-width: 0; max-width: 100%; }

/* On the dance list the arrows sit in a grid cell, so they follow the grid's
   own zoom like every other measurement on it - a constant here would grow
   relative to everything around it as somebody zoomed out. */
.grid--dancelist .sect__move { font-size: calc(1.1rem * var(--grid-scale)); }

/* --- the dance list: the bar's right-hand end, and the add panel -----------

   THE ONLY THING IN THE BAR WITH margin-left: auto, which is what puts it at
   the top right whatever is in the middle of the bar - the title box, Save and
   Cancel all vary in width, so measuring a gap would be measuring the wrong
   thing. `.gridbar` wraps at narrow widths, and this block wrapping to a line
   of its own is the right answer there: five controls squeezed onto one line
   at 390px is how the row controls went wrong. */
.gridbar__right {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
}

/* + Add wears the same pill the other two grids' corner cells wear, because it
   is the same act - and it is a <button> here rather than an <a>, since it
   opens a panel instead of going to a page. .head__add is written for an
   anchor, so the three properties a button does not inherit are said again. */
button.head__add {
    font: inherit;
    font-size: 0.8em;
    font-weight: 650;
    cursor: pointer;
    line-height: 1.4;
}

/* Wider than the mark picker: it holds a select of fifty-six dances, and the
   name of one of them plus its tradition is a long line. */
.picker--add { width: min(26rem, calc(100vw - 1.5rem)); }

/* `.picker__pick` puts its label in a <span> above the control, and there is a
   rule for a select in one but never was for an input - so "Or a heading, or a
   place" rendered with its box beside the words while the dance select above
   it had a line of its own. Found in a render.
   Scoped to this panel rather than added to `.picker__pick select` beside it:
   the dances grid's note input is a `.picker__pick` too, with styling of its
   own that does not set display, and a shared rule would quietly change it. */
.picker--add .picker__pick input {
    display: block;
    width: 100%;
    margin-top: 0.2rem;
    font: inherit;
    font-size: 0.95rem;
    color: var(--ink);
    padding: 0.4rem;
    border: 1px solid var(--line-strong);
    border-radius: 6px;
}

/* The two add forms, one under the other, with a rule between them so they read
   as two questions rather than one form with four boxes in it. */
.picker--add .listadd + .listadd {
    margin-top: 0.7rem;
    padding-top: 0.7rem;
    border-top: 1px solid var(--line);
}

/* Close is not part of the second form, and with nothing between them it sat
   straight under Add it and read as a third button in the same row of
   controls. The gap is what says it belongs to the panel rather than to the
   heading. */
.picker--add .picker__answers--one { margin-top: 0.9rem; }

/* The narrow box and its button on one line, the box first: "Sets 1 [Add the
   dance]" reads as a sentence, where the button above the box reads as
   finished before it is. */
.listadd__go {
    display: flex;
    align-items: flex-end;
    gap: 0.5rem;
    margin-top: 0.5rem;
}
.listadd__go .btn { flex: 0 0 auto; }
.picker__pick--narrow { flex: 0 0 5.5rem; margin-bottom: 0; }
.picker__pick--narrow input,
.picker__pick--narrow select {
    display: block;
    width: 100%;
    margin-top: 0.2rem;
    font: inherit;
    font-size: 0.95rem;
    color: var(--ink);
    padding: 0.4rem;
    border: 1px solid var(--line-strong);
    border-radius: 6px;
}

/* --- personal mode ---------------------------------------------------------

   Reading the sheet, the question is "which ones am I in", so your own column
   is pulled out of the alphabet, pinned beside the dance, and made louder than
   the row it sits in - which is the one place on any of the three grids where
   that is right, because here you are looking for yourself rather than reading
   across.

   The letter is bigger AND bolder AND on a wash: one of the three alone was
   not enough against twenty columns of the same letter. Bigger by a ratio of
   the grid's own scale, never a constant, or it would swell relative to
   everything around it as somebody zoomed out.

   `.grid--dancelist .slot__v` is two classes, so anything that wants to change
   its font-size has to be at least that - hence the table's class is in the
   selector as well as the cell's. This stylesheet has been caught by that
   three times; the notes are in CLAUDE.md. */
.grid--dancelist.grid--personal .slot--you {
    background-image: linear-gradient(var(--you-bg), var(--you-bg));
}
.grid--dancelist.grid--personal .slot--you .slot__v {
    font-size: calc(1.15rem * var(--grid-scale));
    font-weight: 800;
}
/* The wash has to lose to a mark: a cell saying D on a green ground is still a
   D, and the mark's own colour is what says which. So the mark colours are
   re-stated for your column, at a specificity that beats the wash above. */
.grid--dancelist.grid--personal .slot--you.slot--caller        { background-image: linear-gradient(#dbe7fb, #dbe7fb); }
.grid--dancelist.grid--personal .slot--you.slot--lead_musician { background-image: linear-gradient(#d9efe2, #d9efe2); }
.grid--dancelist.grid--personal .slot--you.slot--musician      { background-image: linear-gradient(#eaf6ef, #eaf6ef); }
.grid--dancelist.grid--personal .slot--you.slot--reserve       { background-image: linear-gradient(#fbf0d8, #fbf0d8); }
.grid--dancelist.grid--personal .slot--you.slot--dancer        { background-image: linear-gradient(#f2f3f7, #f2f3f7); }

/* And your own name at the head of it, in the same voice as the letters below.
   `.grid--dancelist .head--you` sets the size, so this names both classes. */
.grid--dancelist.grid--personal .head--you .vert {
    font-weight: 800;
    color: var(--navy-deep);
}

/* A cell nobody can change does not offer a pointer. `.grid--dancelist .slot`
   sets `cursor: pointer` for the builder, so this has to name both classes to
   beat it - the same specificity lesson three other rules in this file record.
   Not the events grid's `is-readonly`: app.js adds and removes that one around
   the live grid swap, and a class another page's script writes is not a class
   this page can lean on. */
.grid--dancelist.grid--reading .slot { cursor: default; }

/* --- the dance list's corner, and the add panel laid out like one thing -----

   THE CORNER SPLIT. The other two grids put the badge in the upper part of
   this cell and the column label along the bottom; this one puts which dance
   out the list is for up there instead. A flex column with the two ends pushed
   apart does it, and it is on a DIV inside the cell rather than on the cell:
   `display: flex` on a <th> stops it being a table cell, which this grid has
   already been bitten by once - a colspan silently ignored, 438px inside a
   1050px table.

   `height: 100%` resolves because `.grid .head` sets an explicit height. */
.head__corner {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: 0.3rem;
    height: 100%;
}

/* Which dance out, in the grid's own type rather than the bar's: it is part of
   the sheet now. It wraps, because a name and a date do not fit a column that
   is 17rem at full zoom and less below it - and `overflow: hidden` so a long
   one is cut off at the top of the cell rather than pushing the label out of
   the bottom of it. */
/* CENTRED IN THE SPACE ABOVE THE LABEL, both ways: it takes whatever height is
   left over and puts its lines in the middle of it, which is where the badge
   sits on the other two grids. Left and top, it read as a caption that had
   drifted up out of the sheet; centred, the corner is a title block.
   `flex: 1` does the vertical half, the inner column the horizontal, and
   `min-height: 0` lets a long name be clipped by the overflow rather than
   pushing the label out of the bottom of the cell. */
.head__what {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    margin: 0;
    min-height: 0;
    overflow: hidden;
    /* The date, and the size the whole block is judged against. It and the
       name above it were set at the size of a caption, on a page whose subject
       they are - the corner is the one place on the sheet with room to say
       what this is, so it says it at a size somebody reads rather than
       checks. */
    font-size: calc(0.95rem * var(--grid-scale));
    line-height: 1.3;
    color: var(--ink-soft);
}

/* THE NAME IS THE THING AND THE DATE CONFIRMS IT, so they are two lines in two
   voices rather than one run of text at one size. Both blocks, because a
   `<span>` in flow would let the date come up beside the name the moment the
   name was short enough - and then two parts of a weekend would run into each
   other. A part after the first is spaced off the one above it; the first is
   not, or the whole block would sit low in the cell. */
.head__for { display: block; }
.head__for + .head__for { margin-top: 0.35em; }

.head__ev {
    display: block;
    font-size: calc(1.2rem * var(--grid-scale));
    font-weight: 700;
    line-height: 1.15;
    color: var(--ink);
    text-decoration: none;
}
.head__ev:hover { text-decoration: underline; }
.head__when { display: block; }

/* The sheet's own name, and it has to be allowed to wrap: "Dance List - v2" in
   a column that is 80px at the bottom of the zoom range cannot share a line
   with + Add. `.head__label` is a flex row, so the name is the item that
   gives, and + Add keeps its width. */
.head__name {
    min-width: 0;
    font-size: calc(1rem * var(--grid-scale));
    font-weight: 650;
    color: var(--ink);
}

/* The draft pill, in a grid cell rather than in the bar, so every measurement
   on it follows the grid's own zoom - `.pill` is fixed in rem, which at the
   bottom of the range would have left it the one thing on the sheet that had
   not shrunk. */
.pill--grid {
    font-size: calc(0.72rem * var(--grid-scale));
    padding: calc(0.08rem * var(--grid-scale)) calc(0.45rem * var(--grid-scale));
    white-space: nowrap;
}

/* The two add forms one under the other, each shaped the same way: the label
   and its box across the full width, then the narrow box and the button on a
   line below. `.listadd` is a wrapping flex ROW - it was written for a strip
   under the grid - so inside this panel the dance form wrapped (its select is
   wide) and the heading form did not, and the two ended up different shapes
   with the "As" box sitting up beside a label. A column here, and they line
   up because they are built the same. align-items: stretch, or a column's
   flex-end would shrink each child to its own content width; gap 0, because
   the children carry their own margins. */
.picker--add .listadd {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    margin: 0;
}

/* The bin as a real button, for a delete that waits for no Save - the list of
   dance lists. `.binbox` was written for a <label> around a checkbox, so the
   three things a <button> does not inherit are said here; the hover and focus
   colours come off .binbox above and are shared.

   flex: none, because .mine__item is a flex row and a button that can shrink
   ends up an oval. */
.binbox--go {
    flex: none;
    border: 0;
    background: none;
    font: inherit;
}
.binbox--go:hover .bin        { color: #a3231c; }
.binbox--go:focus-visible .bin { outline: 2px solid var(--navy-mid); outline-offset: 2px; }

/* --- a dancer who has dropped out -----------------------------------------

   THE COLUMN STAYS AND TURNS RED. The columns on a dance list are whoever said
   yes or maybe, so somebody changing their answer to no used to take their
   column away and every letter in it with it - silently, leaving a sheet that
   looked finished and a set that was a dancer short. The hole has to be
   visible, and it has to be visible as a fault rather than as decoration:
   there is nothing to celebrate in a column that needs re-doing.

   Painted as a background-image gradient, not a background-color, for the
   reason the mark colours already record: `.grid tbody tr.row--alt
   td:not(...)` is three elements and four classes and outranks anything a
   single class can say. */
.grid--dancelist .head--dropped {
    background-image: linear-gradient(#fdecea, #fdecea);
}
.grid--dancelist .head--dropped .vert {
    color: #a3231c;
    font-weight: 700;
    text-decoration: line-through;
    text-decoration-thickness: 1px;
}
/* Where the M or the m sits on everybody else's column. A dropped-out one has
   no music to advertise, and the space is already reserved on every column, so
   it costs nothing and reads at a glance down a row of twenty names. */
.grid--dancelist .head--dropped .head__plays {
    color: #a3231c;
    font-weight: 800;
}

/* ONLY THE CELLS WITH A LETTER IN THEM. An empty square in that column has
   nothing to replace; reddening it would spread the alarm down the whole sheet
   instead of pointing at the rows that are actually short. */
.grid--dancelist .slot--dropped {
    background-image: linear-gradient(#fdecea, #fdecea);
}
.grid--dancelist .slot--dropped .slot__v {
    color: #a3231c;
    text-decoration: line-through;
    text-decoration-thickness: 1px;
}

/* Their own counts along the bottom: what they WERE down for, so greyed rather
   than reddened. The red is for the letters somebody has to do something
   about. */
.grid--dancelist .slot--was .slot__v { color: var(--ink-soft); opacity: 0.6; }
