@import url('https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,600;9..144,700&family=Inter:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap');

:root {
  /* Greens and tan sampled directly from the logo */
  --pine: #39483A;          /* logo background green */
  --pine-dark: #2B372C;     /* deeper green for hovers and headings */
  --brass: #A89878;         /* logo tan */
  --brass-deep: #857552;    /* darker tan where contrast is needed */

  --bg: #F6F4ED;
  --card: #FFFFFF;
  --ink: #262E27;
  --ink-soft: #5A655B;
  --line: #E3DFD3;
  --slate: #7C93A3;
  --danger: #9E4630;

  /* This WAS the room palette. It is not any more (update 218): room
     colours are free hex values chosen per room and stored in the
     database, and the seven house colours now live in ROOM_COLOURS in
     src/views.ts, which is the only authority for them. Keeping a second
     copy here would be two lists free to drift apart.
     --tag-2 survives because two rules that have nothing to do with rooms
     use it as an accent green: the notice banner border and the completed
     wizard step. */
  --tag-2: #4F7A6B;  /* sage - accent green, no longer a room colour */

  --radius: 10px;

  --today-bg: #E9F1EA;      /* today's calendar cell */

  /* How much of a house photo shows through, on the picker card and in the
     upload dialog's preview (update 314).

     ONE NUMBER, IN ONE PLACE, AND THAT IS THE ONLY REASON IT IS A
     VARIABLE. Two rules draw a faded photo - .house-card.has-photo::before
     and .photo-preview::before - and they have to agree exactly or the
     preview stops being a preview. Tuning it here changes both.

     WHY FADE AT ALL, since a dark scrim with white text is the usual
     answer. Because the card's type is already --pine-dark and
     --ink-soft on white, and a scrim would mean the picker drew some cards
     with dark text and some with light depending on whether an admin had
     got round to uploading anything. Fading the photo toward --card
     instead leaves every colour on this page exactly where it was, and it
     is what makes the camera legible over a photo without needing chrome
     of its own.

     0.28 IS LOW ON PURPOSE. It reads as a card with a picture in it rather
     than a picture with a card over it, and a 20px Fraunces house name
     stays comfortably readable over a busy photo - which is the thing that
     has to survive, since the name is what the card is for. Raise it and
     check the worst photo anyone has uploaded, not the best. */
  --photo-fade: 0.28;

  /* logo 32px + 14px padding top and bottom + 1px border */
  /* logo 44px + 14px padding top and bottom + 1px border */
  --topnav-h: 73px;

  /* How tall the calendar's scrolling pane is. Six week rows plus the
     weekday strip, so any month fits without scrolling within itself. */
  --calpane-h: 620px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 15px;
  line-height: 1.5;
}

h1, h2, h3 {
  font-family: 'Fraunces', Georgia, serif;
  color: var(--pine-dark);
  font-weight: 600;
  margin: 0 0 0.4em;
}

.mono { font-family: 'IBM Plex Mono', monospace; }
a { color: var(--pine); }

.container { max-width: 1080px; margin: 0 auto; padding: 0 20px 60px; }

.topnav {
  /* Same hex the logo's background was flattened to, so the image edges
     are invisible against the bar. */
  background: #39483A;
  border-bottom: 1px solid #2B372C;
  margin-bottom: 28px;
}
.topnav-inner {
  max-width: 1080px; margin: 0 auto; padding: 14px 20px;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
}
.topnav-left { display: flex; align-items: center; gap: 12px; min-width: 0; }
.brand-link { text-decoration: none; flex-shrink: 0; display: flex; align-items: center; }
/* 44px, not 32 (update 204). The logo is now a STACKED mark - roof above
   the wordmark - so only ~39% of its height is the word "Roomee". At 32px
   that word rendered 12px tall against the old logo's 29px, which is too
   small to read. 44px puts it back to 17px and STILL saves 95px of width,
   because the aspect went from 6.6:1 to 2.6:1.

   IF YOU CHANGE THIS NUMBER, CHANGE --topnav-h TO MATCH:
   logo height + 14px padding top and bottom + 1px border. .cal-key,
   .page-nav and .page-sections .card all position themselves off that
   variable, and they will sit under a header of the wrong height with
   nothing erroring to tell you. */
.brand-img { height: 44px; width: auto; display: block; }

/* The two pills, stacked: who is in tonight above, which house below
   (update 207). Named .topnav-pills after grepping.

   align-items: flex-start IS REQUIRED, not tidiness. Both pills are
   inline-flex, and the default align-items: stretch BLOCKIFIES them - each
   becomes a full-width bar spanning the header. Same trap as the
   inline-block note in the working notes, same one-line fix.

   THE HEIGHTS BELOW ARE ARITHMETIC, NOT TASTE. Two pills at 20px with a
   4px gap is exactly 44px, which is exactly the logo height, which is what
   keeps --topnav-h at 73px. Change a padding, a border or the gap here and
   the header silently grows - and .cal-key, .page-nav and
   .page-sections .card all position themselves off that variable. Each
   pill is 14px line-height + 2px padding top and bottom + 1px border. */
.topnav-pills {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 4px; min-width: 0;
}

.house-count {
  display: inline-flex; align-items: center; gap: 6px;
  background: rgba(246, 244, 237, 0.1);
  border: 1px solid rgba(246, 244, 237, 0.2);
  color: #EFEBE0;
  border-radius: 999px; padding: 2px 11px 2px 9px;
  font-size: 11.5px; line-height: 14px; font-weight: 600; white-space: nowrap;
}
.house-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--brass); flex-shrink: 0;
}

/* Which house you are looking at (update 204). Sits UNDER the person's
   name at the top right, so the two read as one identity block: who you
   are, and where you are. Deliberately shaped like .house-count rather
   than a nav link - it is a statement of where you are that happens to be
   clickable, not another destination.

   Only renders for people whose email belongs to more than one house.
   Everyone else never sees it, and /houses redirects them away if they
   find the URL - which is why it can sit on the green bar without costing
   anyone on roomee.me any room. When it is absent the counter simply sits
   alone in .topnav-pills, vertically centred against the logo, which is
   what every roomee.me user will see.

   Named .house-name after grepping: .house-count and .house-dot were
   already taken, and .room-list has bitten this project once already. */
.house-name {
  display: inline-flex; align-items: center;
  background: rgba(246, 244, 237, 0.1);
  border: 1px solid rgba(246, 244, 237, 0.2);
  color: #EFEBE0; text-decoration: none;
  border-radius: 999px; padding: 2px 11px;
  font-size: 11.5px; line-height: 14px; font-weight: 600; white-space: nowrap;
}
.house-name:hover { background: rgba(246, 244, 237, 0.18); color: #F6F4ED; }

/* The house picker's cards (update 293). One card per house, side by side.
   .house-list is gone with the stacked column of buttons it spaced out;
   nothing else used it. Its old comment said the current house rendered
   disabled, which stopped being true at 206 - a second reason not to keep
   the rule around.

   THE CARD IS THE BUTTON, so these rules dress a <button>, not a div, and
   have to say things a div would inherit for free - font-family: inherit
   is undoing the UA stylesheet, and every one of background, border,
   padding and colour below is overriding a UA default rather than starting
   from nothing. Buttons rather than links because switching houses changes
   server state and has to be a POST. See housePickerPage in views.ts.

   FLEX WITH A FLEX-BASIS, NOT A GRID, and that is what saves a media
   query: the cards sit side by side for as long as there is room for
   200px each and wrap to one per row when there is not, so a phone gets
   the stacked layout without this file having to name a width at which it
   happens.

   min-width: 0 is the flex spelling of the minmax(0, 1fr) this file uses
   on its grids, and is needed for the same reason - a flex item's floor is
   its content, so without it a long house name would set the row's minimum
   width and overflow the screen rather than wrapping. overflow-wrap on the
   name handles the other half of that: a single unbroken word, which no
   amount of min-width would help with. Same pair, same reasons, as
   table.plain above.

   THE NAME AND THE LINE UNDER IT ARE CENTRED IN A TALLER CARD (293a for
   the height and the centring, 293b for the second line coming back).
   Centring needs all three of justify-content (the block axis, because
   this is a column), align-items (the inline axis) and text-align (a name
   that WRAPS, which the first two say nothing about). Miss the third and a
   two-line house name centres as a block but ranges left inside itself.

   MIN-HEIGHT, NOT HEIGHT. 120px is roughly twice what one line of
   Fraunces plus the padding comes to, which is what turns a bar back into
   a card - but a long name has to be free to wrap past it and take the
   card with it, and a fixed height would clip instead. The row stays even
   regardless: flex items stretch to the tallest by default. */
.house-pick { max-width: 640px; margin: 60px auto 0; }
/* THE HEADING AND THE ACCOUNT BUTTON ON ONE LINE (update 309a).
   ~
   baseline, NOT center: the button's text should sit on the same line as
   "Which house?" rather than being centred against a 30px Fraunces cap
   height, which reads as slightly high on every screen.
   ~
   THE h1 KEEPS ITS OWN 0.4em BOTTOM MARGIN and this row adds none, so the
   space below the heading is exactly what it was before the button
   existed. Zeroing it - the obvious tidy - would close that gap by about
   12px and move every card on the page up, which is a layout change
   nobody asked for dressed up as a fix. A flex item's bottom margin
   counts towards the line height, so the button still lands on the
   heading's baseline.
   ~
   IT WRAPS RATHER THAN SHRINKING. On a narrow phone the button drops onto
   its own line under the heading, which is the same thing the old
   foot-of-page link did and better than a squeezed heading. flex-wrap
   costs nothing when there is room. */
.house-pick-head {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: 10px 14px;
}
/* ROW GAP LARGER THAN COLUMN GAP (update 304c). One number served both
   until the cards grew buttons underneath: 14px between two cards side by
   side is right, and 14px between a Delete button and the card starting
   the next row is not - the button reads as belonging to the card below
   it rather than the one above.
   ~
   Two values rather than a margin on the slot, so nothing has to be
   subtracted again at the end of the list. */
.house-cards { display: flex; flex-wrap: wrap; gap: 34px 14px; margin-top: 18px; }
/* Heading over each block, and only drawn when BOTH blocks exist - a lone
   heading over the only block on the page answers a question nobody asked.
   Quiet: these separate two groups of cards, they do not compete with the
   "Which house?" above them. */
.house-block-head {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 15px; font-weight: 600; color: var(--ink-soft);
  margin: 22px 0 0;
}
.house-block-head:first-of-type { margin-top: 10px; }
/* THE SLOT IS THE FLEX ITEM FROM UPDATE 304, not the card. Buttons had to
   go UNDER each card, and the card is a <button> - so they cannot go
   inside it. The slot is the box that holds both and takes the 200px the
   card used to take.
   ~
   align-items: stretch so the card fills the slot's width without needing
   width:100% of its own.
   ~
   position: relative FROM 314, so the camera button can be positioned
   against it. The camera is a SIBLING of the card, not a child - a button
   inside a button is invalid HTML and the reason the action buttons went
   under the card in the first place - so it needs an offset parent that is
   not the card. The slot's top edge IS the card's top edge, which is why
   top/right positioning lands on the card regardless of whether there are
   action buttons below. */
.house-slot { position: relative; flex: 0 0 200px; display: flex; flex-direction: column; gap: 8px; }
.house-slot .house-card { flex: 1 1 auto; }
/* Side by side, Disable on the left. Equal columns rather than
   content-width, so a slot with one button and a slot with two line up
   along the bottom instead of stepping. */
.house-slot-actions { display: flex; gap: 8px; }
/* .house-slot-actions .btn AND NOT .house-slot-btn ALONE (update 304a).
   A single class has the SAME SPECIFICITY as .btn, and .btn is declared
   further down this file - so a bare .house-slot-btn lost every property
   it shared with .btn and the buttons rendered at full size. Nothing looks
   wrong in the rule; the rule simply never applies.

   THIS IS THE THIRD TIME THIS FILE HAS CAUGHT THAT TRAP - see
   .house-card.current:hover above and .btn.danger:hover below, both of
   which carry their own note about it. Those two were solved by ordering,
   which works and depends on the rule staying where it was put. Two
   classes is the version that cannot be broken by a later edit, and it is
   what this one uses.

   SIZING MATCHES .link-remove-btn, the Remove button on the family links
   tables: 4px 10px at 12px. Smaller than .branch-delete-btn, which this
   rule tried first - a branch row is a full-width strip where its button
   is the only control, whereas these sit two-up under a 200px card and
   have to stay subordinate to the card itself. The card is the thing being
   read; these are the things being noticed second.
   ~
   Worth knowing that .link-remove-btn gets away with being a SINGLE class
   only because it is declared after .btn. It is the same fragile
   arrangement described above, working by luck of position rather than by
   design. Do not copy that half of it. */
.house-slot-actions .btn {
  flex: 1 1 0; min-width: 0;
  padding: 4px 10px; font-size: 12px; text-align: center;
}
.house-slot-actions .btn[disabled] { opacity: 0.45; cursor: not-allowed; }
/* Why the Enable button is off, under the button rather than as a tooltip:
   a disabled control with no stated reason is the thing people report as
   broken. */
.house-slot-note { font-size: 12px; color: var(--ink-soft); margin: 0; line-height: 1.35; }
/* A DISABLED HOUSE IS A DIV, NOT A BUTTON - nobody may enter it, and a
   card that looks pressable and does nothing reads as broken. So the hover
   and the pointer both have to come off explicitly: .house-card sets
   cursor:pointer and .house-card:hover sets a background, and neither
   knows this one is inert.
   ~
   #F4F2ED and dashed IS .branch-row.is-disabled's own treatment, reused
   rather than re-invented - "retired, not gone" already means something
   in this stylesheet and it should look the same wherever it appears.
   Muted rather than struck through or greyed to unreadable, for the same
   reasons given there: the house still exists, the
   name still has to be readable, and the button beneath it has to look
   pressable. */
.house-card-off { cursor: default; background: #F4F2ED; border-style: dashed; }
.house-card-off:hover { border-color: var(--line); background: #F4F2ED; }
.house-card-off .house-card-name { color: var(--ink-soft); }
/* The note in a disabled card can be a whole sentence rather than one
   word - a visitor gets "This house has been disabled. Contact the house
   administrator." in place of the admin's "Disabled", because there is
   nothing underneath a visitor's card to carry it. At the default line
   height three centred lines sit too loosely to read as one message. */
.house-card-off .house-card-note { line-height: 1.35; }
/* THE ONE DISABLED CARD THAT IS PRESSABLE (update 309c): an admin at the
   house cap, whose card links to the plans instead of explaining itself in
   a grey paragraph underneath. Everything above assumes .house-card-off is
   a dead div, so three things have to be given back.
   ~
   1. It is an <a>, and an anchor does not inherit .house-card's centring
      or lose its underline on its own - the same three properties
      .house-card-add has to restate for the same reason.
   2. .house-card-off:hover flattens the hover deliberately, because a dead
      card lighting up under the pointer reads as broken. This one is NOT
      dead, so the hover comes back - and it has to be restated rather than
      inherited, since that rule sits above this one with equal
      specificity plus a class.
   3. The name stays --ink-soft like its dead siblings. The house really is
      disabled; the card is an offer about it, not a live house, and
      colouring the name would put it back among the enterable ones. */
.house-card-off-upgrade {
  text-decoration: none;
  cursor: pointer;
}
.house-card-off-upgrade:hover {
  border-style: solid; border-color: var(--brass); background: var(--card);
}
.house-card-off-upgrade:hover .house-card-name,
.house-card-off-upgrade:hover .house-card-note { color: var(--pine-dark); }
/* The offer is the point of this card, so it carries the weight its
   siblings' one-word "Disabled" does not need. */
.house-card-off-upgrade .house-card-note { font-weight: 600; color: var(--pine); }
.house-card {
  /* FIXED WIDTH, NOT 1 1 200px (update 303g). They used to grow to fill
     the row, so two houses gave two wide cards and three gave three narrow
     ones - the same house changing size depending on how many neighbours
     it had, and changing again when the picker split into two blocks with
     different counts in each. 200px is what a three-across row produced,
     which is the proportion these were designed at.
     ~
     Cards no longer fill the row, so a block of two sits left-aligned with
     space to its right. That is correct: the space is where the next house
     goes.
     ~
     position: relative FROM 314, for the photo's ::before below. It has no
     other effect here: nothing inside a card was absolutely positioned
     before, so there was no ancestor this could have stolen. */
  position: relative;
  min-width: 0; min-height: 120px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  text-align: center; font-family: inherit; cursor: pointer;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 18px 20px; color: var(--ink);
}
.house-card:hover { border-color: var(--brass); background: #FFFDF8; }
.house-card:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }
/* Fraunces at 20px, matching .branch-name - the same job in the same
   family of pages, so it reads as a heading inside the card rather than as
   button text. */
.house-card-name {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 20px; font-weight: 600; color: var(--pine-dark);
  overflow-wrap: anywhere; line-height: 1.2;
}
/* The line that says the card DOES something rather than just naming a
   house. Quiet, at .hint's size and colour, because the name is the thing
   being read and this is the thing being understood once. */
.house-card-note { font-size: 13px; color: var(--ink-soft); }
/* Where you are now. Marked, NOT disabled: arriving here from a sign-in
   link, "this one" is a legitimate answer, and a card that does nothing
   when pressed reads as broken (update 206). The inset shadow rather than
   a thicker border, so the card does not change size when it is the
   current one and the row stays even.

   THE BACKGROUND IS var(--today-bg), NOT A NEW COLOUR (293b). That is the
   pale green the calendar already fills today's cell with, and it already
   means exactly this in Roomee - "this is the one you are on". Inventing a
   fifth pale tint for the same idea would have been a second answer to a
   question this palette has answered once.

   .house-card.current:hover EXISTS BECAUSE IT HAS TO. It has the same
   specificity as .house-card:hover and comes later, so without a hover of
   its own the current card would win the background outright and sit dead
   under the pointer while its neighbour lit up - on the one card the
   reader is most likely to try first. This is the identical trap that
   .btn.danger:hover further down this file exists for; that one cost a
   session to find, so it is named here rather than left to be rediscovered
   on a third rule. */
.house-card.current {
  background: var(--today-bg); border-color: var(--pine);
  box-shadow: inset 0 0 0 1px var(--pine);
}
.house-card.current:hover { background: #DCE8DE; border-color: var(--pine-dark); }

/* ---- The house photo on the card (update 314) ---- */
/* ONE PHOTO PER HOUSE, faded toward --card so the card's own type colours
   still read over it. See --photo-fade in :root for why a fade rather than
   a dark scrim with white text.

   A ::before RATHER THAN background-image ON THE CARD ITSELF, and this is
   the whole reason the rule has a pseudo-element at all: opacity applies
   to a BOX, not to one background layer. Fading the card would fade the
   name and the note with it. A separate layer can be faded on its own and
   the text left alone.

   border-radius: inherit IS WHAT GIVES THE CORNERS. The photo is
   background-size: cover on a box inset to nothing, so it fills the card
   and whatever falls outside is cropped - which is the accepted trade -
   but without this line it would be cropped to a rectangle and the card's
   10px corners would show four square photo corners poking out.

   inset: 0 RESOLVES AGAINST THE PADDING BOX, so the photo sits INSIDE the
   1px border rather than under it. That is the right way round: the border
   stays a border on a photographed card exactly as it is on a plain one,
   and the row stays even.

   pointer-events: none, or the layer would sit between the pointer and the
   button it is decorating and the card would stop responding to clicks in
   its own middle. */
.house-card.has-photo::before {
  content: '';
  position: absolute; inset: 0;
  border-radius: inherit;
  background-image: var(--house-photo);
  background-size: cover;
  /* WHERE THE COVER CROP SPENDS ITS SLACK (update 315). The card is about
     5:3, so a wider photo has room to slide left and right and a taller
     one up and down; --house-photo-pos names the point that stays put.

     THE FALLBACK IN THE var() IS THE WHOLE COMPATIBILITY STORY. Every
     photo uploaded under 314 has no photo_focus, so the view writes no
     property and this resolves to 50% 50% - which is exactly what
     background-position: center meant before. Nothing needed backfilling.

     Percentages, not pixels, and that matters: a percentage positions the
     image relative to the OVERFLOW rather than to the box, so the same
     stored value stays sensible when a card is taller than its neighbours
     because somebody's house name wrapped to two lines. */
  background-position: var(--house-photo-pos, 50% 50%);
  opacity: var(--photo-fade);
  pointer-events: none;
}
/* THE TEXT HAS TO BE LIFTED, and it is not optional. A positioned
   pseudo-element with z-index: auto paints in the positioned-descendant
   layer, which is ABOVE ordinary inline content - so without this the name
   and the note would be behind the photo. Giving the spans a position and
   a z-index puts them back on top.

   The child selector rather than the two class names, so a third span
   added to a card in future is covered without anybody having to notice
   this rule exists. */
.house-card.has-photo > span { position: relative; z-index: 1; }
/* A PHOTOGRAPHED CURRENT CARD LOSES ITS PALE FILL AND KEEPS ITS RING.
   --today-bg under a photo is a green cast over somebody's picture, which
   is neither the photo nor the "you are here" signal - it is a muddle of
   the two. The inset --pine ring and the border carry the meaning on their
   own, and they are the parts a photo cannot interfere with.

   THIS IS THE ONE VISIBLE THING A PHOTO CHANGES BEYOND THE PICTURE, which
   is why an admin who wants the fill back has a Remove in the dialog - see
   POST /houses/photo/delete.

   Both states restated, hover included, for the reason .house-card.current
   itself needs a hover: these have the same specificity as the rules above
   and only win by coming later, so a missing one would silently fall back
   to the wrong background rather than to nothing. */
.house-card.has-photo.current { background: var(--card); }
.house-card.has-photo.current:hover { background: var(--card); border-color: var(--pine-dark); }
/* Same reason on the ordinary hover: .house-card:hover sets #FFFDF8, which
   under a photo is a barely-there shift that reads as the card flickering.
   The border colour is doing the hover work on these. */
.house-card.has-photo:hover { background: var(--card); }

/* ---- The camera (update 314) ---- */
/* Change this house's photo. A SIBLING of the card positioned over its top
   corner, never a child - see .house-slot above and camera() in views.ts
   for why a button cannot go inside a button.

   ALWAYS DRAWN, NOT ON HOVER. On a bare card it is the only thing that
   says a photo is possible at all; on a dressed one it is the only route
   to changing or removing it. A hover-only control would also be
   unreachable on a phone, which is where the photos are.

   z-index: 2 PUTS IT ABOVE BOTH OTHER LAYERS - the photo's ::before at
   auto and the lifted spans at 1. Source order alone would settle it, but
   three stacked layers on one card is exactly where an unstated
   assumption gets broken by a later edit.

   THE BACKGROUND IS TRANSLUCENT WHITE RATHER THAN --card. It has to be
   readable over whatever photo is behind it, and a solid disc would read
   as a sticker dropped on the picture. At 0.86 over a photo already faded
   to --photo-fade there is plenty of contrast for a 1.4px stroke.

   28px IS BELOW THE 44px TOUCH TARGET Roomee aims at elsewhere, and that
   is a knowing trade: a target that size in the corner of a 200px card
   would cover a fifth of it and would be pressed by accident by anybody
   reaching for the card itself. The card is the important control here and
   this one must not compete with it. */
.house-photo-btn {
  position: absolute; top: 8px; right: 8px; z-index: 2;
  width: 28px; height: 28px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: 999px;
  background: rgba(255, 255, 255, 0.86); color: var(--ink-soft);
  cursor: pointer;
}
.house-photo-btn:hover { border-color: var(--brass); color: var(--pine-dark); background: #fff; }
.house-photo-btn:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }
.house-photo-icon { width: 16px; height: 16px; display: block; }
/* Add a house (303). An <a>, not a <button>, because it sits inside the
   switcher's form - so it needs the three properties an anchor does not
   inherit from .house-card: no underline, and the flex centring that a
   button gets from the browser's default alignment and an anchor does not.

   DASHED BORDER, AND NO OTHER DIFFERENCE. It reads as "a space where a
   house could go" rather than as a fourth kind of house, and it borrows
   .house-card's size, padding and hover outright so the row stays even
   when it wraps. Resisting a distinct colour here is deliberate: the row
   already carries one meaningful tint (--today-bg, "you are here"), and a
   second would make the reader work out which of two things each shade
   means. */
.house-card-add {
  text-decoration: none;
  /* 2px, AND A LITERAL RATHER THAN A PALETTE VARIABLE (update 303h).
     At 1px in --line the dashes were almost invisible and the card read as
     an empty box rather than as an invitation. 303g fixed that with 2px in
     --ink-soft, which was prominent and too heavy - a dashed outline that
     dark competes with the solid borders of the real houses beside it.
     ~
     #9AA29B sits about midway between --ink-soft and --line in the same
     grey-green family: light enough to read as an outline waiting to be
     filled, dark enough to see. It is a literal because the palette has
     nothing between those two and this is the only thing that wants one -
     the same call the one-off tints further down this file make.
     ~
     STILL NO COLOUR OF ITS OWN. A tan or a green here would compete with
     .house-card.current, which is the one card in this row that already
     means something by its colour. */
  border-width: 2px;
  border-style: dashed;
  border-color: #9AA29B;
  /* The border grew by 1px on each side, so the padding gives it back -
     otherwise this card is 2px taller and wider than its neighbours and
     the row goes ragged. */
  padding: 17px 19px;
  gap: 2px;
}
.house-card-add .house-card-name { color: var(--ink-soft); }
/* THE PLUS REPLACES THE "Set up a new one" LINE, which said nothing the
   card's own title did not. A plus is the one symbol that needs no
   caption, so it is aria-hidden and the name carries the meaning for a
   screen reader.
   ~
   line-height: 1 because a 34px glyph in a flex column otherwise reserves
   a 34px line box plus leading and pushes the card taller than the fixed
   houses beside it. */
.house-card-plus {
  font-size: 34px; line-height: 1; font-weight: 300; color: var(--ink-soft);
}
/* Needed for the same reason .house-card.current:hover is: .house-card:hover
   sets a background and a border COLOUR, and border-style is not among
   them - so without this the dashes survive the hover while everything
   else changes, which reads as a rendering fault rather than a state.
   The colour is restated because :hover sets --brass and would otherwise
   win over the --ink-soft above regardless of the style change. */
.house-card-add:hover { border-style: solid; border-color: var(--brass); }
.house-card-add:hover .house-card-name,
.house-card-add:hover .house-card-plus { color: var(--pine-dark); }

/* THE ADD CARD AT THE HOUSE CAP (update 309, reworded at 309b). Same slot,
   same dashed outline, a sentence where the plus was - so the row does not
   change shape when the cap is reached and the card is recognisably the
   one that used to say Add a house.
   ~
   NO RED AND NO WARNING COLOUR. Being at the cap is an ordinary state of a
   working plan, not an error, and .conflict-banner's vocabulary belongs to
   things that went wrong. The wording says the same: it is an offer to
   upgrade rather than a notice of a refusal.
   ~
   The note is a CONTINUATION of the name above it - "Upgrade" then "to add
   more houses!" - so it takes the same 13px the enterable cards' notes
   take rather than the 12px a standalone caption would. Dropping it a size
   would break the sentence across two registers. */
.house-card-upgrade .house-card-note {
  color: var(--ink-soft); line-height: 1.35;
}
.house-card-upgrade:hover .house-card-note { color: var(--pine-dark); }

@media (max-width: 480px) {
  /* FULL WIDTH ON A PHONE. The fixed 200px above is right on a laptop and
     wrong here: at 375px it leaves a single left-aligned card with half
     the screen empty beside it, which reads as a layout fault rather than
     as room for another house. This restores the filling behaviour the
     cards had before 303g, at the only width where it was the right
     answer. */
  .house-slot { flex: 1 1 100%; }
}

/* ---- Add a house: the three house settings (update 303e) ---- */
/* Timezone, currency and week start on one line. They were a stacked
   column of three full-width selects, which pushed the room section below
   the fold on a laptop for three controls holding a zone, a symbol and a
   weekday.

   THREE EQUAL COLUMNS, AND THE ROW IS CAPPED AT THE WIDTH OF THE ROOM GRID
   BELOW IT. 507px is not a round number chosen for looks - it is
   .room-grid's own width, 92 + 193 + 70 + 116 plus its three 12px gaps.
   The two grids sit one above the other on the Add a house page, so a row
   that ran wider than the room form read as a mistake rather than as a
   difference.

   An earlier version gave the timezone a 1fr column on the grounds that
   its labels are sentences where the other two are a symbol and a weekday.
   That was true and it looked wrong: one field three times the width of
   its neighbours dominated a row of three equal-weight choices. The zone
   clips instead, which costs nothing - the dropdown shows every label in
   full the moment it is opened, and the selected zone is readable from its
   city name alone.

   minmax(0, 1fr) and not a bare 1fr: an auto minimum lets the widest
   option in a select push the grid wider than its container, which is the
   rule every grid in this file follows. Here it is what makes the cap
   hold. */
.house-basics {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 4px 12px;
  align-items: end;
  max-width: 507px;
}
.house-basics label { display: block; margin-bottom: 4px; }

@media (max-width: 820px) {
  /* THE ZONE GETS ITS OWN ROW AND THE OTHER TWO STAY PAIRED - the same
     call the rate bands got at 247e and the closure dates at 247d. A
     symbol and a weekday are short enough that a row each would spend the
     axis a phone has least of on fields that do not want it, while a
     timezone label is the one thing here that genuinely needs the width. */
  .house-basics { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .house-basics .hb-zone { grid-column: 1 / -1; }
}

/* justify-content: flex-end, not space-between (update 208). The panel
   still takes flex: 1 so it owns all the width left over by .topnav-left -
   that is what gives flex-end something to push against - but its two
   children, the links and the user block, now sit together at the right
   rather than being thrown to opposite ends of the bar. The pills stay
   left, because they are in .topnav-left and not in here at all.

   Harmless on mobile: inside the burger the panel becomes a COLUMN whose
   height is set by its content, and justify-content has nothing to
   distribute along an axis with no spare room. The rule below only
   overrides flex-direction and align-items for that reason. */
.nav-panel { display: flex; align-items: center; gap: 22px; flex: 1; justify-content: flex-end; }
.nav-links { display: flex; gap: 22px; align-items: center; }
.nav-links a { text-decoration: none; color: rgba(246, 244, 237, 0.72); font-size: 14px; font-weight: 500; }
.nav-links a:hover { color: #F6F4ED; }
.nav-links a.active { color: var(--brass); }
.nav-user { font-size: 13px; color: rgba(246, 244, 237, 0.7); display: flex; gap: 10px; align-items: center; }
.nav-user .btn.secondary {
  color: #F6F4ED; border-color: rgba(246, 244, 237, 0.4); background: transparent;
}
.nav-user .btn.secondary:hover { background: rgba(246, 244, 237, 0.1); }

/* Burger: hidden on desktop, drives the menu via :checked on mobile */
.nav-toggle {
  position: absolute; opacity: 0; width: 1px; height: 1px;
  margin: 0; overflow: hidden;
}
.nav-toggle:focus-visible + .nav-burger { outline: 2px solid var(--brass); outline-offset: 3px; border-radius: 4px; }
.nav-burger { display: none; }

@media (max-width: 820px) {
  .topnav { margin-bottom: 18px; }
  .topnav-inner { padding: 12px 16px; flex-wrap: wrap; }

  .brand-img { height: 36px; }
  /* Same arithmetic as desktop, one size down: 2 x 18px + 4px gap = 40px,
     which is now what sets the header height rather than the 36px logo -
     hence --topnav-h 65px above, not 61px. */
  .house-count { padding: 1px 9px 1px 7px; line-height: 14px; gap: 5px; }
  .house-name { padding: 1px 9px; line-height: 14px; }
  .topnav-left { gap: 9px; }

  .nav-burger {
    display: flex; flex-direction: column; justify-content: center; gap: 5px;
    width: 44px; height: 44px; padding: 10px; margin: -10px -10px -10px 0;
    cursor: pointer; -webkit-tap-highlight-color: transparent;
  }
  .nav-burger span {
    display: block; height: 2px; width: 100%; border-radius: 2px;
    background: #E6E1D4; transition: transform 0.2s ease, opacity 0.2s ease;
  }
  .nav-toggle:checked + .nav-burger span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  .nav-toggle:checked + .nav-burger span:nth-child(2) { opacity: 0; }
  .nav-toggle:checked + .nav-burger span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

  .nav-panel { display: none; }
  .nav-toggle:checked ~ .nav-panel {
    display: flex; flex-direction: column; align-items: stretch;
    flex-basis: 100%; gap: 0; margin-top: 10px;
    border-top: 1px solid rgba(246, 244, 237, 0.18); padding-top: 6px;
  }
  .nav-links { flex-direction: column; align-items: stretch; gap: 0; }
  .nav-links a {
    padding: 13px 4px; font-size: 16px;
    border-bottom: 1px solid rgba(246, 244, 237, 0.14);
  }
  .nav-links a.active { color: var(--brass); font-weight: 600; }
  .nav-user {
    justify-content: space-between; padding: 14px 4px 6px; font-size: 14px;
  }
  .nav-user .btn { padding: 9px 16px !important; font-size: 14px !important; }
}

.card { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 22px; margin-bottom: 20px; }
.grid-2 { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; }
@media (max-width: 800px) { .grid-2 { grid-template-columns: 1fr; } }

label { display:block; font-size: 13px; font-weight: 600; color: var(--ink-soft); margin-bottom: 4px; }
input, select {
  width: 100%; padding: 9px 11px; border: 1px solid var(--line); border-radius: 7px;
  background: #FCFBF8; font-family: inherit; font-size: 14px; color: var(--ink);
}
input[type="checkbox"], input[type="radio"] { width: auto; margin: 0; }
.field { margin-bottom: 14px; }
.btn {
  display: inline-block; background: var(--pine); color: #fff; border: none; border-radius: 7px;
  padding: 10px 18px; font-size: 14px; font-weight: 600; cursor: pointer; text-decoration: none;
}
/* [hidden] IS DISPLAY:NONE IN THE BROWSER'S OWN STYLESHEET, AND THE LINE
   ABOVE BEATS IT (update 265). An author rule always out-ranks a UA rule,
   so `display: inline-block` on .btn means button.hidden = true does
   NOTHING - the button stays exactly where it was.

   That had two live consequences before this rule existed:

     * the read-only dialog (263) showed a Back button beside its OK, both
       doing the same thing, because notify() sets back.hidden = true;
     * both Add age band buttons (247) stayed visible after the last blank
       band was revealed, because the reveal handler sets button.hidden.

   The same trap is already handled twice in this file for .rate-band and
   for .stay-row / .lock-row / .person-row, each of which sets its own
   display. This is the third and it is the general one: EVERY .btn in
   Roomee has the problem, so fixing it at .btn fixes the two above and
   every future one, rather than needing a fourth entry here each time.

   Anything that sets its own display on a .btn from now on must think
   about this again - the rule below only covers the display that .btn
   itself declares. */
.btn[hidden] { display: none; }
.btn:hover { background: var(--pine-dark); }
.btn.secondary { background: transparent; color: var(--pine); border: 1px solid var(--pine); }
.btn.danger { background: var(--danger); }
/* Danger buttons had no hover state at all: .btn:hover and .btn.danger have
   equal specificity, and .btn.danger wins only by coming later - so it also
   beat the hover. Named explicitly here rather than reordered, because the
   confirmation dialog's main action is one of these and a destructive
   button that does not respond to the pointer feels broken. */
.btn.danger:hover { background: #863a27; }
/* .setting-note (update 239) is the right-hand column on Admin -> General
   settings, and is the same thing .hint is: quiet explanatory prose. It is
   added to this selector rather than given its own copy of these two
   declarations, on the principle this file follows throughout - a second
   copy of the same rules is free to drift, a shared selector list cannot. Its LAYOUT rules live with .setting-row. */
.hint, .setting-note { color: var(--ink-soft); font-size: 13px; }

.tag {
  display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px 4px 6px;
  border-radius: 5px 12px 12px 5px; color: #fff; font-size: 12.5px; font-weight: 500;
  white-space: nowrap; position: relative;
}
.tag::before { content: ''; width: 7px; height: 7px; border-radius: 50%; background: rgba(255,255,255,0.55); flex-shrink: 0; }
/* No .tag-1 .. .tag-7 any more (update 218). A room's colour is a free hex
   the admin picks, so it arrives as an inline style attribute on the tag
   itself - a class cannot carry an arbitrary value. .tag-none is still a
   class because "no room" is one fixed appearance, not a chosen one. */
.tag-none { background: #B8B2A2; }
/* White text and a white dot are the default, and they are unreadable on a
   pale background - white on pale yellow is a contrast ratio of 1.12. When
   the chosen colour is too light for white, views.ts adds this class and
   the tag switches to dark ink. See needsDarkInk() for where the line is
   drawn and why it is not simply "whichever reads better". */
.tag-light { color: var(--ink); }
.tag-light::before { background: rgba(0, 0, 0, 0.32); }

.room-list { display: flex; flex-direction: column; gap: 10px; }
.room-row {
  display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
  padding: 10px 4px; border-bottom: 1px dashed var(--line);
}
.room-row:last-child { border-bottom: none; }
.room-meta { font-size: 12.5px; color: var(--ink-soft); }

.cal-header { display:flex; align-items:center; justify-content:space-between; margin-bottom: 10px; }
.cal-grid, .cal-dowrow { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px; }
.cal-dow { text-align:center; font-size: 11px; color: var(--ink-soft); text-transform: uppercase; letter-spacing: 0.05em; padding-bottom: 4px; }
.cal-cell { min-height: 92px; min-width: 0; border: 1px solid var(--line); border-radius: 8px; padding: 6px; background: #FCFBF8; }
.cal-cell.empty { background: transparent; border-color: transparent; }
.cal-cell-head { display: flex; align-items: baseline; justify-content: space-between; gap: 4px; flex-wrap: wrap; }
.cal-cell .daynum { font-family: 'IBM Plex Mono', monospace; font-size: 13px; font-weight: 600; color: var(--ink); }

/* how many people are staying that night */
.daycount {
  font-family: 'IBM Plex Mono', monospace; font-size: 10.5px; font-weight: 500;
  color: var(--ink-soft); background: rgba(57, 72, 58, 0.08);
  border-radius: 999px; padding: 0 5px; line-height: 16px; flex-shrink: 0;
}
.daycount.full { background: var(--brass); color: #fff; }
.cal-cell.past .daycount { opacity: 0.45; }
.cal-cell.today {
  /* Deeper tan and a heavier stroke, so today reads from across the grid
     without needing a louder fill. */
  border-color: var(--brass-deep); border-width: 3px;
  background: var(--today-bg);
}
.cal-cell.today .daynum { color: var(--pine-dark); font-weight: 700; }
.cal-tags { margin-top: 4px; display: flex; flex-direction: column; gap: 3px; min-width: 0; }
/* Calendar tags wrap instead of forcing the column wide */
.cal-tags .tag {
  font-size: 10.5px; padding: 3px 7px 3px 5px;
  max-width: 100%; min-width: 0;
  white-space: normal; overflow-wrap: anywhere; line-height: 1.25;
  align-items: flex-start;
}
.cal-tags .tag::before { margin-top: 3px; }

.conflict-banner {
  background: #FBEDE8; border: 1px solid var(--danger); color: var(--danger);
  border-radius: 8px; padding: 12px 16px; margin-bottom: 18px; font-size: 13.5px;
}

.pref-list { list-style: none; margin: 0; padding: 0; }
.pref-item {
  display: flex; align-items: center; gap: 10px; padding: 10px 12px; border: 1px solid var(--line);
  border-radius: 8px; margin-bottom: 8px; background: #FCFBF8; cursor: grab;
}
.pref-item .handle { color: var(--ink-soft); }

/* Reorder buttons - the only way to do this on a touch screen */
.pref-move { display: flex; gap: 4px; flex-shrink: 0; }
.pref-move button {
  width: 34px; height: 34px; padding: 0;
  border: 1px solid var(--line); border-radius: 7px;
  background: var(--card); color: var(--pine-dark);
  font-size: 15px; line-height: 1; cursor: pointer;
}
.pref-move button:hover { background: #F1EFE8; border-color: var(--brass); }
.pref-move button:active { background: #E7E4DA; }

@media (max-width: 820px) {
  /* the drag handle would imply something that doesn't work on touch */
  .pref-item .handle { display: none; }
  .pref-move button { width: 40px; height: 40px; font-size: 17px; }
}
/* room name with its bed description underneath */
/* align-items: flex-start stops the name tag stretching to the width of
   the description beneath it - a flex column stretches children by default */
.pref-room { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; min-width: 0; }
.pref-rank { font-family: 'IBM Plex Mono', monospace; color: var(--brass-deep); font-weight: 600; width: 20px; }

table.plain { width: 100%; border-collapse: collapse; font-size: 13.5px; }
table.plain th, table.plain td { text-align: left; padding: 8px 6px; border-bottom: 1px solid var(--line); }
table.plain th { color: var(--ink-soft); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: 0.03em; }

.empty-state { text-align:center; color: var(--ink-soft); padding: 30px 10px; }

/* ---- Editable people rows (admin) ---- */
/* Seven fields will not sit on one line at any sensible width, so each
   person is laid out over two lines inside their own card:

     Name   Email             [Disable] [Delete]
     Born   Owner Room        Branch

   (The diagram said "Role" until 319 and had done since 291 removed it,
   which is how long a picture in a comment can be wrong without anybody
   noticing. Owner and Owner room are one grid cell - see .person-ownership
   below.)

   Both lines run across the same THREE columns since 319b, so they finish
   at the same right-hand edge and every field is the width of the one
   above or below it. The buttons sit in the third column, pushed to its
   end so their right edge lands on Branch's below, and bottom-aligned
   within their line so they sit level with the inputs beside them rather
   than their labels. Every field carries its own label (unlike the stay
   rows, which share one header) because a two-line layout has no single
   row of headings to align to. */
.person-list { display: flex; flex-direction: column; gap: 8px; }
/* ONE GRID, TWO USERS (update 291): the editor rows AND the Add person form
   above them. They were different shapes until 291 - the add form was a
   stack of .grid-2 pairs - which is why the same six fields appeared in one
   order at the top of the page and another below it.

   A SELECTOR LIST, NOT A COPIED BLOCK, for the reason .guest-check and
   .setting-checkbox share theirs: two copies of a grid definition are two
   things free to drift, and drifting is precisely the fault being fixed.
   Only the chrome below is .person-row's alone - the add form is not a card.

   THE ROLE COLUMN IS GONE (291). role stopped being editable when admin
   creation left the UI, so the area it held went to branch, and the two
   ownership fields took the last two. There were still four equal columns
   at that point; 292 changed the widths, and its own note below is the
   current one. */
.person-row, .person-add-row {
  display: grid;
  /* THREE COLUMNS SINCE 319b, and four from 292 until then. The count was
     never arbitrary: it was the number of fields on the SECOND line, which
     was Born, Branch, Owner, Owner room. 319 folded the two ownership
     fields into one cell, so that line is three cells wide - and so is the
     first, which has always been Name, Email and the buttons. Every field
     on the card is now the same width as the one above or below it, and
     every gap between them is the same 10px.

     THE FIXED 64px IS GONE WITH IT. 292 added it to hold the tick and
     nothing else; 319a moved the tick into Email's column and left it
     empty on the second line, where it was the extra 74px between Owner
     room and Branch that this update removes. Nothing else was using it.
     Every column is ~25px wider as a result, which is the whole card
     changing size and is what makes this a layout change rather than a
     nudge.

     THE THIRD COLUMN HAS A FLOOR, AND THE NUMBER IS NOT ARBITRARY: 240px
     is two .person-action-btn at 116px plus the 8px flex gap between them.
     Those buttons are flex: none and will not shrink (310b), so a column
     narrower than 240 does not squeeze them, it lets them overflow to the
     left across Email. As three bare 1fr columns this happened between
     821px - the narrowest this layout is ever used - and about 845px,
     where 1fr finally reaches 240 on its own. Above that the floor never
     binds and all three columns are equal; at 821px Branch is 240 and the
     other two are 228.

     THIS IS THE THIRD PLACE THAT KNOWS THE BUTTON WIDTH, after
     .person-action-btn itself and 310b's note. If those buttons are ever
     resized, this floor moves with them.

     BREAKPOINT ARITHMETIC, recalculated rather than assumed. At 821px the
     grid gets roughly 716px after page and card padding; take 2 gaps
     (20px) and the three columns have ~232px each before the floor is
     applied, which comfortably holds a date input and a select at 13px.
     Below 820px the whole thing is a flex column and none of this applies.
     If the card padding ever changes, redo this before assuming 820 still
     holds.

     NAME AND EMAIL ARE ONE COLUMN EACH so they match. Email spanned two
     until 292, which made it double the width of the field beside it for
     no reason anybody chose. Delete no longer spans anything - it has a
     column of its own now - and is still justify-self:end inside it, so it
     still sits hard against the right edge exactly as before. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(240px, 1fr);
  grid-template-areas:
    "name email     del"
    "born ownership branch";
  gap: 10px;
  align-items: start;
}
.person-row {
  padding: 12px;
  border: 1px solid var(--line); border-radius: 8px; background: #FCFBF8;
}
/* The add form is not a card, so it has no padding to separate it from the
   Add person button underneath - which left the button sitting against the
   second row of fields. A little more than .person-list's 8px, because that
   gap separates like from like and this one separates a set of fields from
   the button that submits them. */
.person-add-row { margin-bottom: 14px; }
.person-name   { grid-area: name; }
.person-email  { grid-area: email; }
.person-born   { grid-area: born; }
.person-branch { grid-area: branch; }

/* THE TWO OWNERSHIP FIELDS IN ONE CELL (update 319, resized at 319a,
   rationale rewritten at 319b).
   ~
   THE CARD IS THREE COLUMNS AND THIS LINE HAS FOUR FIELDS. Born, Owner,
   Owner room, Branch - so one pair has to share a column, and these two
   are the pair that belongs together: a tick and the field it governs,
   which is why app.js shows and hides one with the other.
   ~
   319's reason was different and is worth keeping, because it is what
   stops anybody "simplifying" this back into four grid areas. The card had
   a fixed 64px third column then, sized for the tick, and the first line
   shares these columns - so the 64px could not move to position 2 without
   making Email 64px wide. That column is gone at 319b, but the wrapper is
   not: four fields still do not fit three columns.
   ~
   THE PAIR IS EXACTLY AS WIDE AS EMAIL ABOVE IT (319a), because it is one
   column like everything else. The room select takes whatever the tick
   leaves, which makes it the narrowest field on the card - deliberately,
   since the pair has one column to live in.
   ~
   THE TICK'S COLUMN IS auto, NOT A NUMBER. It is the word "Owner" that
   sets the width here, not the 16px box, so max-content is the honest
   measurement and it follows the label if it is ever reworded.
   ~
   6px, NOT THE 10px OF THE GRID AROUND IT. This gap separates a tick from
   the field it governs; the 10px one separates unrelated fields.
   ~
   align-items: start matches the outer grid, so the tick and the select
   sit on their own labels rather than being stretched to the taller of
   the two. */
.person-ownership {
  grid-area: ownership;
  min-width: 0;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 6px;
  align-items: start;
}

/* The Owner checkbox (update 290, moved to a cell of its own at 291).

   AT 290 it sat under the Role select sharing that cell, with its own text
   beside it. At 291 role left the page, so it took a grid area of its own -
   and its text went with the move, because the cell now carries a
   .field-label saying "Owner" above it exactly like every neighbouring
   field. Two words saying Owner in one cell is what the move would
   otherwise have produced.

   THE BARE BOX IS LABELLED BY for/id, NOT BY ADJACENT TEXT. That is the
   only thing naming it for a screen reader now, so the pairing in views.ts
   is load-bearing rather than tidiness.

   min-height MATCHES A SIBLING INPUT (7px padding twice, 13px text, 1px
   border twice = 31px), so the box sits on the same line as the selects
   either side of it instead of floating at the top of the row. Recompute it
   if .person-field's input padding ever changes.

   display:flex, not inline-flex: below 820px .person-row becomes a column
   flex container, where inline-block is blockified.

   A DIV, not a label, since 291. It was a label at 290 because it held the
   word "Owner" beside the box. The word moved above it into a .field-label
   with a for/id pairing, so a second label wrapping the same input would
   give it two associations and contribute no text to either. */
.owner-check {
  display: flex; align-items: center;
  min-height: 31px;
}
.owner-check input {
  flex-shrink: 0; width: 16px; height: 16px; margin: 0;
}

.person-field { min-width: 0; }
.person-field input, .person-field select { padding: 7px 9px; font-size: 13px; }
/* Beats the global .field-label { display: none } - see the stay rows. */
/* flex, not block, since 310a: the Name label carries the Admin tag beside
   it, and baseline keeps the pill sitting on the word rather than centred
   against it. */
.person-row .field-label, .person-add-row .field-label {
  display: flex; align-items: baseline; gap: 6px;
  font-size: 11px; font-weight: 600;
  color: var(--ink-soft); margin-bottom: 3px;
}
/* Which row belongs to the admin (update 291). INFORMATION, NOT A CONTROL -
   there is no way to change a role on this page any more, and without this
   the page stopped saying who the admin was at all. That matters because
   /admin/people/delete refuses to remove the last admin, and the refusal
   would otherwise arrive with nothing on screen to explain it. */
/* ON THE LABEL LINE SINCE 310a, not under the input. Under the input it
   made the top grid row taller than its neighbours, and .person-actions is
   bottom-aligned to that row - so the admin's buttons sat lower than
   everybody else's. No margin-top now: it is inline content in a flex
   label. */
.person-admin-tag {
  display: inline-block;
  padding: 1px 7px; border-radius: 999px;
  font-size: 10px; font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase;
  /* Literal, not a variable: there is no pale brass in :root and inventing
     one for a single rule would put a second palette beside ROOM_COLOURS. */
  color: var(--brass-deep); background: #F1EADC;
}
/* THE TWO ROW ACTIONS, IN ONE CELL (update 310a). Disable and Delete were
   two grid children stacking on top of each other; they are now a flex row
   inside the `del` area, side by side with Disable first.
   ~
   align-self: end KEEPS THEM BOTTOM-ALIGNED with the name and email
   inputs, which is what the single button did before. It only works
   because the admin tag moved onto the label line at 310a - under the
   input it made this grid row taller and dragged the buttons down with it,
   so the admin's Delete sat visibly lower than everybody else's. */
.person-actions {
  grid-area: del; align-self: end; justify-self: end;
  display: flex; gap: 8px;
}
/* A FIXED 116px EACH, NOT `width: 100%; max-width: 116px` (fixed at 310b).
   ~
   The percentage was the bug. The single Delete button filled its cell and
   was capped at 116px; TWO of them shared the same cell and each took half
   of it, so adding Disable made Delete shrink. The cell had room for both
   at full size - nothing had to give, and the width was simply being
   computed from the wrong thing.
   ~
   flex: none SO THEY NEVER SHRINK AGAIN. A flex item's default is to give
   up width under pressure, which is exactly how this looked correct with
   one button and wrong with two.
   ~
   THAT REFUSAL TO SHRINK IS WHY THE GRID HAS A FLOOR ON ITS THIRD COLUMN
   (319b). 116 + 8 + 116 = 240, and a column narrower than that does not
   compress these two, it lets them overflow leftwards. If this width ever
   changes, change minmax(240px, 1fr) with it. */
.person-action-btn {
  width: 116px; flex: none; padding: 8px 0; font-size: 13px;
}

@media (max-width: 820px) {
  .person-row, .person-add-row {
    display: flex; flex-direction: column; align-items: stretch; gap: 8px;
  }
  /* The ownership pair unpacks with everything else (update 319). Left as
     its own grid it would be the one place on a phone where two fields sat
     side by side, and its columns are a desktop measurement with nothing
     to do with this width. Column, 8px, exactly like the row around it -
     which is what these two did before 319 as well, since they were plain
     children of that column then. */
  .person-ownership {
    display: flex; flex-direction: column; align-items: stretch; gap: 8px;
  }
  /* Below 820px the row is a column, so the pair stays a row of two
     natural-width buttons at the left rather than two full-width bars. */
  .person-actions { align-self: flex-start; justify-self: auto; }
  .person-action-btn {
    width: auto; max-width: none; padding: 8px 16px;
  }
}

/* People still needing a branch - a to-do list, not an error */
.branch-todo { color: var(--brass-deep); font-weight: 600; }

/* THE NOTE THAT SAYS A SECTION'S FEATURE IS SWITCHED OFF (update 272).
   ~
   ONE CLASS FOR ALL THREE OF THEM - Branches, Timeshare and Nightly rates.
   It was three: .branch-off-note here from 232, .rate-off-note from 247,
   .tshare-off-note from 271, each with a comment explaining that they were
   deliberately kept apart because the three settings make different
   promises and a shared selector would make "keep them identical" the
   default the day one of them should not be.
   ~
   THAT ARGUMENT WAS TESTED AND IT LOST. What actually happened over forty
   updates is what the argument permitted rather than what it predicted:
   the three drifted into three different-looking notes saying the same
   kind of thing on one page. Branches was an underlined paragraph, rates
   and timeshare were boxes - and the rates one had ended up INSIDE its own
   faded block, so the one sentence explaining why everything was greyed
   was itself greyed at 0.55. Nobody chose any of that; each was a
   reasonable local decision.
   ~
   The prediction was that one of them would need to look different. In
   forty updates none did. So the classes are merged, and if one ever does
   need to differ, that is a modifier on this - never a fourth copy.
   ~
   A BOX, not a rule underneath. The boxed form is the one that survives
   being moved to the top of a card: an underlined paragraph directly below
   a heading reads as part of the heading's furniture, where a filled box
   reads as a thing that has been put there.
   ~
   IT IS NEVER FADED, and every caller must keep it OUTSIDE the .is-off
   wrapper it describes. It is the only thing on the card that explains why
   the rest is unusable, and fading the explanation along with the thing it
   explains is how a greyed section becomes a mystery. Same rule the
   .setting-row note follows further down this file. */
.section-off-note {
  margin: 0 0 16px; padding: 9px 12px;
  border: 1px solid var(--line); border-radius: 7px; background: #F4F2ED;
}
/* THE ONE THAT CARRIES A BUTTON (update 310h). The People cap note has an
   Upgrade button in it; the other three have nothing to press, because a
   switched-off feature is fixed on Settings and this one may not be
   fixable by the reader at all.
   ~
   A MODIFIER ON .section-off-note, NOT A FOURTH COPY - which is exactly
   what the comment above asked for the day one of them needed to differ.
   ~
   The sentence and the button sit on one line and the sentence takes the
   slack; on a narrow screen the button drops under it rather than
   squeezing the text into a column. */
.people-cap-note {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 8px 14px;
}
.people-cap-note > span { flex: 1 1 260px; }

/* ---- Editable branch rows (admin) ---- */
/* Only three fields plus a delete button, so unlike the people rows these
   do fit on one line, with a shared header above them. */
.branch-head-row,
.branch-row {
  display: grid;
  grid-template-columns: minmax(150px, 1.6fr) minmax(150px, 1.4fr) 80px 82px;
  gap: 8px;
  align-items: center;
}
.branch-head-row {
  padding: 0 12px 6px;
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--ink-soft);
}
.branch-list { display: flex; flex-direction: column; gap: 8px; }
.branch-row {
  padding: 10px 12px;
  border: 1px solid var(--line); border-radius: 8px; background: #FCFBF8;
}
.branch-field { min-width: 0; }
.branch-field input, .branch-field select { padding: 7px 9px; font-size: 13px; }
.branch-count { font-variant-numeric: tabular-nums; }
.branch-delete-btn { width: 100%; padding: 8px 0; font-size: 13px; }

/* ---- House closures on Manage house (update 242) ---- */
/* Modelled on .branch-row above, but flex rather than grid: a branch row
   is a form with three editable fields that have to line up in columns,
   and a closure row is two lines of text and one button. Nothing here
   needs a column to align with anything in the row below it, so a grid
   would only be four track definitions to keep in step for no gain.

   .room-meta inside is deliberate reuse, not an accident: it is already
   the small grey line under a name in .lock-who on this very page, which
   is exactly the job it is doing here. */
/* Each row is an editable form as of 243, so this is modelled on .lock-row
   above rather than on .branch-row: same flex-wrap shape, same
   align-items: flex-end so the buttons sit on the baseline of the inputs
   beside them, same reduced input padding. Two rows on this page that do
   the same job should look like they do.

   The measurements are NOT shared with .lock-row - copied, not aliased.
   That row has a fixed 230px name block and a wider Room select; this one
   has a free-text reason that should take the slack instead. Aliasing them
   would tie two layouts together that only happen to agree today. */
.closure-list { display: flex; flex-direction: column; gap: 8px; }
.closure-row {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px;
  padding: 12px;
  border: 1px solid var(--line); border-radius: 8px; background: #FCFBF8;
}
.closure-form {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px;
  margin: 0; flex: 1 1 auto; min-width: 0;
}
/* The two date fields share a flex-basis so they come out the same width,
   which is the same thing .field-pair does for the Add form above - the
   mechanism differs because this row wraps and that one is a grid, but the
   rule it is keeping is the one rule: a pair of dates is a pair. */
.closure-field { flex: 1 1 140px; min-width: 128px; }
.closure-field.closure-reason { flex: 2 1 200px; min-width: 160px; }
.closure-field label { font-size: 11px; margin-bottom: 3px; }
.closure-field input { padding: 7px 9px; font-size: 13px; }
.closure-actions { display: flex; gap: 6px; flex: 0 0 auto; }
.closure-actions .btn { padding: 8px 14px; font-size: 13px; }
/* Full-basis, so it always takes a line of its own under the fields
   however the row above it has wrapped. */
.closure-meta { flex: 1 1 100%; margin: 0; display: flex; align-items: center; gap: 8px; }

/* Closures that are over. Muted like a disabled branch, and for the same
   reason: still real, still worth reading, no longer acting on anything.
   Still fully editable - a closure recorded with the wrong dates is worth
   correcting after the fact, and nothing about editing it brings it back
   into force. */
.closure-row.is-past { background: #F4F2ED; border-style: dashed; }
.closure-past {
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--ink-soft); background: rgba(57, 72, 58, 0.08);
  border-radius: 999px; padding: 1px 7px; flex-shrink: 0;
}

@media (max-width: 820px) {
  /* Stacked, at the same breakpoint .lock-row uses - the two rows sit in
     the same column on the same page and must not reflow at different
     widths. The buttons share the width rather than stretching one across
     it. */
  .closure-row { flex-direction: column; align-items: stretch; }
  .closure-form { flex-direction: column; align-items: stretch; }
  .closure-field, .closure-field.closure-reason { flex: 1 1 100%; }
  .closure-actions .btn { flex: 1; }
}

/* ---- Nightly rates on the Admin page (update 247) ---- */
/* Modelled on .closure-row above, and copied from it rather than aliased
   to it - the same decision that row made about .lock-row. A closure row
   is one line of fields; a rate period is a header line plus a stacked
   ladder of age bands, so it is a column that CONTAINS a wrapping row
   rather than being one. Aliasing would tie two layouts together that
   only agree at the top.

   .room-meta and .empty-state inside are deliberate reuse: they are
   already the small grey line and the nothing-here line everywhere else,
   which is exactly the job they do here. */
.rate-list { display: flex; flex-direction: column; gap: 12px; }
/* CAPPED, and this is most of what stopped the tab looking messy. Two
   date fields and a two-column price ladder stretched across the full
   width of a card read as a form that had lost half its fields - a lot of
   white space either side of a handful of characters. 640px is about the
   width the fields actually want, and it lines up with the 520px the
   Settings rows cap themselves at without pretending to be the
   same measurement: this one carries three fields across, that one
   carries one. */
/* ONE WIDTH, SET HERE, AND EVERYTHING INSIDE FILLS IT (update 247c).
   ~
   It was 640px on the row with a separate 460px cap on the band panel
   inside it, which is two magic numbers that had to agree and did not -
   the panel sat visibly narrower than the header above it, and the
   buttons on the end of that header spilled out past the panel's right
   edge. The header now ends where the panel ends because neither of them
   has a width of its own to disagree with.
   ~
   500px is about what two date fields, a night count and a three-column
   band row want, and no more. */
.rate-period {
  max-width: 500px;
  padding: 12px;
  border: 1px solid var(--line); border-radius: 8px; background: #FCFBF8;
}
/* The Add form is not inside a .rate-period, so it takes the cap itself.
   Same number in two places rather than a shared class, because the two
   are capped for the same reason and would still be capped for that
   reason if one of them ever changed shape. */
.rate-form { margin: 0; max-width: 500px; }
/* The header line: dates, branch, and the two buttons. Wraps like a
   closure row and at the same flex-basis, so a rate period and a closure
   break at the same width - the two are the same shape of thing seen on
   two pages. */
.rate-head {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px;
}
/* SCOPED TO .rate-head, AND THAT SCOPE IS THE BUG FIX (update 247a).
   ~
   These three were on a bare .rate-field, which is also the class the age
   band rows use - and those rows are a GRID with fixed 96px and 150px
   tracks. A min-width of 128px on a grid item does not shrink to its
   track; it overflows it. So From age drew 128px wide inside a 96px
   column and sat on top of Rate per night, which is exactly what it
   looked like.
   ~
   The lesson is the general one about sharing a class between a flex row
   and a grid row: flex-basis and min-width mean "negotiate" in one and
   "override the track" in the other. Either scope them, as here, or use
   two class names. */
.rate-head .rate-field { flex: 1 1 130px; min-width: 118px; max-width: 200px; }
/* Wider than the dates and past the 200px cap on .rate-field above,
   because a branch name is words where a date is digits. Truncating the
   one control on the row whose value you cannot guess from its first half
   would be the wrong economy. */
.rate-head .rate-field.rate-field-branch { flex: 1 1 180px; min-width: 150px; max-width: 260px; }
.rate-field label { font-size: 11px; margin-bottom: 3px; }
.rate-field input, .rate-field select { padding: 7px 9px; font-size: 13px; }
/* The night count, sitting on the same baseline as the date fields it is
   derived from. Shaped like a .rate-field - a small label over a value -
   so it reads as one more thing on that row rather than as a caption that
   wandered in. flex: 0 0 auto because it is as wide as its own text and
   must not take room the dates want. */
.rate-nights {
  display: flex; flex-direction: column; flex: 0 0 auto; min-width: 0;
}
.rate-nights-label { font-size: 11px; font-weight: 600; color: var(--ink-soft); margin-bottom: 3px; }
/* The padding matches an input's, so the number lands on the same line as
   the text in the boxes beside it rather than floating above it. */
.rate-nights-value { font-size: 13px; padding: 7px 0; white-space: nowrap; }

/* BOTH BUTTONS AT THE FOOT, ACROSS THE FULL WIDTH. They were the last
   item in the flex header, where they took whatever width was left and
   pushed past the band panel underneath - so the row read as two things
   of different widths stacked on each other. A foot of its own is also
   where a Save belongs on a form this tall: at the end of the thing it
   saves, not at the top of it. */
.rate-foot { display: flex; gap: 6px; margin-top: 12px; }
.rate-foot .btn { padding: 8px 14px; font-size: 13px; }

/* The age bands, under the header and inset from it, so a period reads as
   one thing with a list inside rather than as two stacked rows. The rule
   above is what separates them; the left border is what says the ladder
   belongs to the period and not to the card. */
/* The branch picker above the Add form. .subject-picker does the layout -
   it is the same control as the Year picker on My profile and should look
   like it - so this only sets the space under it and caps the width, since
   a branch name is short and a select stretched across the card reads as a
   field that failed to load. */
/* The gap under it is bigger than a normal field's, on purpose. The
   picker governs everything below it - the Add form AND the list - so it
   needs to read as a control over a section rather than as the first
   field of the form directly beneath it, which is what 18px looked like.
   The rule is what actually separates them; the space stops the rule
   feeling tight. */
.rate-filter {
  max-width: 340px;
  margin-bottom: 22px; padding-bottom: 20px;
  border-bottom: 1px solid var(--line);
}

/* A CLOSED BOX, not a top rule and a left rule (update 247a). The two
   borders were meant to read as "this list belongs to the period above
   it", and on the Add form - which has no period row around it - they
   read as a line down the side of nothing, which is what they were.
   ~
   A full border with a white fill works in both places: inside a period
   row, which is cream, it reads as an inset panel; on the Add form it
   reads as a panel. And it gives the Add age band button somewhere to be
   that is visibly INSIDE the bands and not another button in the column
   below the form. */
.rate-bandlist {
  margin: 14px 0; padding: 12px;
  border: 1px solid var(--line); border-radius: 8px; background: #FFF;
  display: flex; flex-direction: column; gap: 6px;
}
/* The band rows have no min-width of their own, so the fixed tracks
   above decide the widths and nothing overflows them. Stated rather than
   left to inheritance, because a bare .rate-field rule reappearing above
   would silently break the row again. */
.rate-band .rate-field { min-width: 0; max-width: none; }
/* Column captions, shown once at the top instead of on every row - the
   same arrangement .branch-head-row uses above. The per-row .field-label
   copies are display:none by the global rule and un-hidden only below
   820px, where the rows stack and a single caption line no longer lines
   up with anything. */
/* FIXED TRACKS, not fractions. An age is two characters and an amount is
   five, so 1fr each gave them a third of a card apiece and left the row
   looking like it had failed to load. These are the widths the two fields
   want; the auto column takes the button and the row ends where it ends.

   The two selectors MUST keep the same track list or the captions stop
   sitting over their columns - which is the entire job of the caption
   row. */
.rate-band-head, .rate-band {
  display: grid;
  grid-template-columns: 96px 150px auto;
  gap: 8px; align-items: end;
}
/* [hidden] is display:none by the browser's own sheet, which display:grid
   above overrides by specificity - so it has to be said again here, and
   the noscript block in views.ts overrides THIS in turn by source order.
   Three rules about one attribute, and the order they resolve in is the
   whole mechanism. */
.rate-band[hidden] { display: none; }
.rate-band-head {
  font-size: 11px; font-weight: 600; letter-spacing: 0.02em;
  color: var(--ink-soft);
}
/* Plain .btn is the pine green and .btn.danger is the red, both from the
   palette at the top of this file - the small band buttons carry them
   rather than the outlined .btn.secondary they started with. Add band and
   Save read as the same kind of action as every other green button on the
   site, and Remove reads as the same kind of action as every other red
   one. An outlined button in a row of solid ones just looks less
   important than it is. */
.rate-band-actions .btn, .rate-band-add .btn { padding: 7px 12px; font-size: 12.5px; }
/* The Add form's band rows have no button, so their third cell is an
   empty div holding the grid column open. Collapsed rather than left
   there: on desktop it is invisible either way, but on a phone - where
   the button drops to a row of its own - an empty cell would take a grid
   row and a gap for nothing.
   ~
   :empty is exact here and only because the markup writes the div with no
   whitespace inside it. If that ever gains a newline this rule silently
   stops matching, which is worth knowing before reformatting views.ts. */
.rate-band-actions:empty { display: none; }
/* Aligned under the button column rather than the fields, so Add band
   sits directly below the Save and Remove buttons of the rows above it.
   Left as a plain block with a margin, not a fourth grid row: it holds one
   button and has no columns to line up. */
/* Inside the band panel and separated from the rows by a rule, so the
   button that adds a band is visibly part of the bands - the thing that
   made the Add form confusing was three solid buttons stacked in one
   column with nothing saying which belonged to what. */
.rate-band-add { margin-top: 8px; padding-top: 10px; border-top: 1px solid var(--line); }
/* THE WRAPPER GOES WITH THE BUTTON (update 265), and without this it does
   not. The reveal handler in app.js hides the BUTTON once the last blank
   band is uncovered; this div is the button's only parent and carries the
   separator rule and the space above it, so hiding the button alone leaves
   a stray horizontal line and about eighteen pixels of nothing at the foot
   of the band list.

   That was invisible until the .btn[hidden] rule further up this file made
   button.hidden work at all - so the two lines are one fix and the second
   was only ever needed because of the first.

   :has() BECAUSE THE STATE IS SET AT RUNTIME. app.js sets the hidden
   property on the button after the page has rendered, so a class the
   server put on the wrapper could not track it, and having app.js toggle
   the wrapper as well would be a second thing to keep in step - the exact
   pair that drifts. This reads the same attribute that hides the button.
   Same reasoning as the .setting-row rule for a disabled subordinate
   checkbox.

   The child combinator is deliberate: it must match THIS div because of
   ITS OWN button, never because of a hidden button nested somewhere
   deeper. */
.rate-band-add:has(> .btn[hidden]) { display: none; }
/* Sits with the bands it describes rather than at the foot of the whole
   section, which is where it used to be and where nobody reads it. Tight
   against the button above it, because they are one thought. */
.rate-band-hint { margin: 8px 0 0; }
/* The blank row that adds a band. Marked off with a dashed top rule
   rather than a heading: it is one more row of the same ladder, and a
   heading over a single row reads as a section. */
/* .rate-band-new WAS HERE AND IS GONE (update 266). It styled the single
   blank row an existing period used to carry - dashed rule above it,
   because that row was a different kind of thing from the saved bands
   around it and had a Save button of its own.

   Since 266 an existing period offers the same plain blank slots the Add
   form does, so the class no longer appears in any markup and there is
   nothing left for it to distinguish. Removed rather than annotated,
   unlike .page-section: that one is a general mechanism worth keeping
   findable, this was one row's decoration. */

/* The symbol and the amount are one control, so the symbol sits inside
   the field's border rather than beside it. The input carries no border of
   its own and takes the padding off, or there would be two boxes with a
   character between them. */
.rate-amount {
  display: flex; align-items: center; gap: 4px; min-width: 0;
  padding: 0 9px;
  border: 1px solid var(--line); border-radius: 7px; background: #FFF;
}
.rate-symbol { font-size: 13px; color: var(--ink-soft); flex-shrink: 0; }
.rate-amount input {
  flex: 1 1 auto; min-width: 0;
  border: none; background: none; padding: 7px 0; font-size: 13px;
}
.rate-amount input:focus { outline: none; }
/* Focus lands on the wrapper instead, so the whole control lights up the
   way every other field on the page does. :focus-within is what makes the
   two-element control behave as one for the keyboard. */
/* var(--pine), NOT var(--accent) - there is no --accent in :root, and an
   undefined custom property makes the whole declaration invalid at
   computed-value time rather than falling back to anything. The border
   simply did not change on focus, which reads as a rendering quirk rather
   than as the typo it was. */
.rate-amount:focus-within { border-color: var(--pine); box-shadow: 0 0 0 2px rgba(57, 72, 58, 0.12); }


/* A period that has finished. Muted like a past closure and a disabled
   branch, and for the same reason: still real, still worth reading, no
   longer deciding anything. Still fully editable - an expired period is
   the record of what was actually charged, and one recorded wrongly is
   worth correcting after the fact. */
/* A finished rate period, muted the same way a past closure is on Manage
   house and a disabled branch is above. The OVER pill that also marked one
   is gone for good (247b); this is the whole of it now, and it is enough -
   a fact worth one signal was getting three.

   IT IS NOT WHAT PUTS THESE AT THE BOTTOM. That is plain date order in
   ratePeriodsOrdered, which knows nothing about today. The two agree for
   every period that has ended, which is exactly why it is worth saying
   they are different mechanisms: change the sort and this carries on
   working, and the other way round.

   Still fully editable. An expired period is the record of what was
   actually charged, and one recorded wrongly is worth correcting after the
   fact - nothing about editing it brings it back into force. */
.rate-period.is-past { background: #F4F2ED; border-style: dashed; }

/* Everything below the two switches, while nightly rates are switched off.
   Faded and unusable, NOT hidden: this is configuration the flag promises
   to keep, and hiding it would make preserved settings look deleted - the
   same argument .section-off-note settles for all three of them.

   The controls inside carry the `disabled` attribute as well. This is the
   visible half only; it is not the gate, and it is not what stops a
   forged POST - the routes do that. Never let this be the only thing
   standing between a switched-off feature and a write. */
.rate-editors.is-off { opacity: 0.55; }
.rate-editors.is-off .rate-period { background: #F7F5F0; }

/* The Branch management editors, while branches are switched off
   (update 258). Same treatment as .rate-editors above and same 0.55, so
   the two switched-off editors on this page read as one idea rather than
   two similar ones.

   VISIBLE, FADED, UNUSABLE - three states, not two. Hiding was rejected at
   232 and stays rejected: this is configuration use_branches promises to
   preserve, and hidden settings look deleted. What 232 left out is that
   visible need not mean editable, which is what 258 added.

   The controls inside carry `disabled` as well, and .section-off-note sits
   OUTSIDE this block so the one sentence explaining the fade is not
   itself faded.

   NEITHER THIS NOR THE OPACITY IS A GATE. A disabled input is simply not
   submitted; the three routes under /admin/branches are what refuse a
   write, and they had to be added at 258 as well - the flag had shipped
   at 232 with the editor fully working. Never let this be the only thing
   between a switched-off feature and a write.

   A separate selector from .rate-editors rather than a shared one. NOTE
   THAT THE MATCHING ARGUMENT ABOUT THE **NOTES** WAS ABANDONED AT 272 -
   those merged into .section-off-note after three copies drifted into
   three appearances. This one is not the same case and stays separate: a
   note is a sentence and these are the fade rules for a whole block of
   editors, which really do differ - .rate-editors.is-off has to restyle
   .rate-period, .branch-editors.is-off .branch-row, .tshare-editors.is-off
   .tshare-block. A shared selector could not do that without naming all
   three anyway. */
.branch-editors.is-off { opacity: 0.55; }
.branch-editors.is-off .branch-row { background: #F7F5F0; }
/* .rate-off-note WAS HERE AND IS GONE (update 272). It styled two
   different kinds of thing under one name: the "nightly rates are switched
   off" note, which has moved to the shared .section-off-note at the top of
   this file, and two notes about rate SCOPE that have nothing to do with
   the feature being off at all. The second lot are below under a name that
   describes them.

   Its declarations were byte-identical to .tshare-off-note's, so the two
   were already one rule written twice - which is how the third of the
   family came to look different from both without anyone deciding it
   should. See the note on .section-off-note. */
/* The two notes about how rate periods are SCOPED - "rates are set per
   branch but there is no branch to set them for", and the explanation on a
   read-only historic bucket. Same box as .section-off-note, and that is a
   coincidence worth leaving as a coincidence: these two live INSIDE the
   editors and are faded with them, because they explain something about
   the rates rather than about why the card is unusable. If .section-off-note
   ever changes shape, these should not automatically follow. */
.rate-scope-note {
  margin: 14px 0 0; padding: 9px 12px;
  border: 1px solid var(--line); border-radius: 7px; background: #F4F2ED;
}

/* ---- Timeshare blocks (update 271) ---- */
/*
 * A THIRD SET OF NEARLY-IDENTICAL RULES, WRITTEN OUT RATHER THAN SHARED.
 * The rows below really are three different shapes of thing - a block, a
 * rate period and a branch row - so the fade rules have to name each of
 * them and a shared selector would have to name all three anyway.
 *
 * THIS IS NOT THE ARGUMENT THAT WAS MADE ABOUT THE **NOTES** AND LOST.
 * Update 271 shipped .tshare-off-note as a third copy of one sentence's
 * styling on the same reasoning, and by 272 the three copies had drifted
 * into three appearances - so they were merged into .section-off-note. The
 * difference is that those three were the same rule written out three
 * times, and these are three genuinely different rules. If these ever
 * collapse to the same declarations, merge them too.
 *
 * The other thing that IS shared is the night-count arithmetic in app.js,
 * by selector list - because that is behaviour, and two copies of a
 * date calculation is two places to be wrong.
 */
/* One branch's upcoming blocks, gathered (update 281).

   THE CONTAINER IS THE BOX (update 282). It shipped at 281 as a rule down
   the left with every ROW inside it a bordered card, which put two levels
   of visible edge on a page with only one level of meaning: these blocks
   belong to this branch and are saved together. The border now belongs to
   the thing the border is about, and the rows are plain.

   520px rather than the 560px the rows used to take: the branch select
   came off them at 282, and a container sized for a control it no longer
   holds is a container with a column of empty air down its right. */
.tshare-branch-group {
  max-width: 520px;
  margin: 0 0 14px;
  padding: 12px 14px;
  border: 1px solid var(--line); border-radius: 8px; background: #FCFBF8;
}
/* The branch name, and it is doing real work now rather than labelling a
   group - with the select gone this is the ONLY thing on screen saying
   whose nights these are. Sized as a heading and not as the 11px
   all-caps .tshare-group labels above it, which are signposts rather than
   content. */
.tshare-branchname {
  margin: 0 0 10px; font-size: 14px; font-weight: 600;
}
/* A whole container of finished blocks. Muted like a past closure, a past
   rate period and a disabled branch: still real, still worth reading, no
   longer deciding anything.

   ON THE CONTAINER RATHER THAN THE ROW since 282, because past and
   upcoming blocks are never mixed inside one - the page splits them before
   it groups them, so a container is entirely one or entirely the other.
   One border to restyle instead of every row inside it. */
.tshare-branch-group.is-past { background: #F4F2ED; border-style: dashed; }
/* Remove sits ON the row, to the right of the length. flex-end keeps it on
   the same baseline as the inputs however tall their labels get -
   .tshare-head sets that for the row, this stops the button stretching to
   the row's full height on the way. */
.tshare-head .tshare-remove {
  align-self: flex-end; padding: 8px 14px; font-size: 13px;
}
/* A block row: no border, no background, no padding. The container around
   it carries all three now.

   The hairline is on the ADJACENT SIBLING rather than as a bottom border
   on every row, so the last row in a container does not end with a rule
   floating above the Save button. */
.tshare-block { padding: 0; }
.tshare-block + .tshare-block {
  margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--line);
}

/* FIELD LABELS ON THE FIRST ROW ONLY (update 283), where they read as
   column headings for the whole box. Repeating "First night / Last night /
   Length" down six rows of one branch is three words of furniture per row
   saying what the row above already said.

   THE SAME ADJACENT-SIBLING SELECTOR the hairline above uses, and
   deliberately NOT :first-of-type. That pseudo-class counts by ELEMENT
   TYPE, which is exactly the trap that had to be fixed at 274 when the
   past label became a <summary>: it would keep working here only for as
   long as every sibling in the form happened to be a div. `+` asks the
   question actually being asked - is there a row before this one.

   HIDDEN THE .sr-only WAY, NOT display: none. The labels stay in the
   accessibility tree and stay paired to their inputs by for/id, so a
   screen reader still announces "Last night" on row four where a sighted
   reader infers it from the column. display: none would have taken them
   out of the tree and left those inputs anonymous - the standing rule in
   this codebase is a label on every input, and hiding one visually is not
   the same as not having one.

   The first row is therefore taller than the rest, which is what makes the
   labels read as headings rather than as a missing row. */
.tshare-block + .tshare-block .tshare-field label,
.tshare-block + .tshare-block .tshare-nights-label {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
/* The Add form still takes the old cap: it holds a branch SELECT, which the
   rows no longer do, so it is genuinely a wider thing. Same number as
   .tshare-branch-group would be wrong here for once. */
.tshare-form { margin: 0; max-width: 560px; }
/* .tshare-list went at 282. It spaced a column of bordered row-cards; the
   rows are now plain siblings inside one container and space themselves
   with .tshare-block + .tshare-block. */
/* Wraps like a rate period's header and at the same flex basis, so the
   two break at the same width - they are the same shape of thing seen in
   two sections of one page. align-items: flex-end puts the fields on one
   baseline whatever height their labels take. */
.tshare-head {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px;
}
/* SCOPED TO .tshare-head, and the scope is not decoration - it is the
   247a lesson taken as read rather than relearned. A bare .tshare-field
   rule would apply anywhere that class appeared, including inside a grid,
   where min-width does not shrink to a track and overflows it instead.
   There is no grid using this class today. Scoping it now is what stops
   there being a bug the day there is one. */
.tshare-head .tshare-field { flex: 1 1 130px; min-width: 118px; max-width: 200px; }
/* Wider than the dates and past the cap above, because a branch name is
   words where a date is digits. Truncating the one control on the row
   whose value cannot be guessed from its first half would be the wrong
   economy - same reasoning, same numbers, as .rate-field-branch. */
.tshare-head .tshare-field.tshare-field-branch { flex: 1 1 180px; min-width: 150px; max-width: 260px; }
.tshare-field { display: flex; flex-direction: column; }
.tshare-field label { font-size: 11px; font-weight: 600; color: var(--ink-soft); margin-bottom: 3px; }
.tshare-field input, .tshare-field select { padding: 7px 9px; font-size: 13px; }
/* The night count, on the same baseline as the dates it is derived from.
   flex: 0 0 auto because it is as wide as its own text and must not take
   room the dates want. */
.tshare-nights {
  display: flex; flex-direction: column; flex: 0 0 auto; min-width: 0;
}
.tshare-nights-label { font-size: 11px; font-weight: 600; color: var(--ink-soft); margin-bottom: 3px; }
/* The padding matches an input's, so the number lands on the same line as
   the text in the boxes beside it rather than floating above it. */
.tshare-nights-value { font-size: 13px; padding: 7px 0; white-space: nowrap; }
/* Both buttons at the foot across the full width, the arrangement
   .rate-foot arrived at: buttons on the end of a wrapping flex header take
   whatever width is left and spill past whatever sits below them. */
.tshare-foot { display: flex; gap: 6px; margin-top: 12px; }
.tshare-foot .btn { padding: 8px 14px; font-size: 13px; }
/* The label above each half of the block list (update 273): "Current and
   upcoming" over one, "Finished" over the other.

   IT EXISTS BECAUSE MUTING WAS DOING THE WHOLE JOB AND SHOULD NOT HAVE
   BEEN. Until 273 the only thing separating a finished block from a
   coming one was .is-past's grey - which asks the reader to compare every
   date on the page against today to work out where the line falls, and
   asks it in the one place where getting it wrong means handing a branch
   nights it has already had. Now the boundary is stated and the muting is
   the second signal rather than the only one.

   A <p> and not an <h4>: the card already has an h2 and an h3 above this,
   and a fourth level of heading for a two-word label would be structure
   that means nothing to anybody reading with a screen reader. It reads as
   a label, so it is styled as one. */
.tshare-group {
  margin: 18px 0 8px; font-size: 11px; font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-soft);
}
/* THE FIRST LABEL ON THE PAGE: no space above it and no rule, because it
   sits directly under the "Blocks" h3 which brings its own margin, and
   because a rule is a BOUNDARY BETWEEN two groups - there is nothing above
   this one to divide it from.

   AN EXPLICIT MODIFIER, AND IT WAS `.tshare-group:first-of-type` UNTIL
   274. That worked only while both labels were <p> elements. Making the
   past one a <summary> broke it in a way nothing would have reported:
   :first-of-type counts by ELEMENT TYPE, so the summary became the first
   <summary> inside its own <details> and matched too - at (0,2,0) against
   .tshare-group-past's (0,1,0), which is enough to win and strip the
   margin off the boundary. The template names which label is the leading
   one instead, so the rule no longer depends on what the elements happen
   to be.

   The two modifiers are never both applied, so their order here decides
   nothing. */
.tshare-group-lead { margin-top: 0; }
/* The rule above the past half is the visual boundary, and it is on the
   LABEL rather than between the two lists so that it cannot appear with
   nothing beneath it. From 274 that label is a <summary>, so the rule
   travels with the toggle whether it is open or shut. */
.tshare-group-past {
  margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--line);
}
/* Past blocks, collapsed until asked for (update 274).

   <details> DOES THE WORK, so this opens with JavaScript off and is
   keyboard-operable for nothing. Same choice as the collapsed empty months
   on the calendar, and it inherits the same warning: the summary stays
   display: block and NOT flex, because flex on a <summary> has a history
   of breaking the toggle in Safari. Anything that needs laying out is laid
   out on a child, which is what .tshare-past-toggle is for.

   list-style: none plus the -webkit- rule removes the native triangle,
   because the toggle below carries its own +/- and two markers on one row
   is one too many. (275 removed that +/- and rewrote this note; 276 put
   both back.) */
.tshare-past > summary {
  display: block; cursor: pointer; list-style: none;
}
.tshare-past > summary::-webkit-details-marker { display: none; }
/* Show / Hide, on the same line as the word Past (update 275; it was a
   block count until then, and this rule is otherwise unchanged from when
   it was one - see 276).

   IT STAYS INSIDE THE <summary>. That is what makes clicking the word open
   the section. Moving it out to a sibling would look identical and quietly
   stop working - which is the mistake .cal-nothing carries a warning
   about, and the reason this one does too.

   Not uppercase and not letter-spaced, unlike the label it sits beside: it
   is the control rather than the heading, and it should not read as more
   of the title.

   276 PUT THIS BACK TO WHAT IT WAS. 275 also restyled it - pine, semibold,
   12px, underlined on hover - on the reasoning that it now reads as a
   control and should look like one. That was a change nobody asked for,
   riding along with one that was asked for, and it made the row shout
   where the rest of the section murmurs. The words changed; the type did
   not. Anything of that sort belongs in its own update where it can be
   judged on its own.

   TWO REAL SPANS, ONE HIDDEN, rather than CSS content on an ::after.
   Generated text is not reliably announced, cannot be selected, and
   vanishes with the stylesheet - and this is the only thing on the row
   saying it does anything. The <details> already tells a screen reader
   whether it is expanded, so this pair is the sighted half of that. The
   +/- below is a different case: it is decoration echoing a state the
   words beside it already name, so losing it costs nothing. */
.tshare-past-toggle {
  margin-left: 8px; letter-spacing: 0.02em; text-transform: none;
  font-weight: 400; font-size: 12.5px;
}
.tshare-past-toggle::after { content: ' +'; font-family: 'IBM Plex Mono', monospace; }
.tshare-past[open] > summary .tshare-past-toggle::after { content: ' \2212'; }
/* Shut is the default state, so the "Hide" copy is the one that starts
   hidden. Written this way round rather than as [open]/:not([open]) pairs
   so that a browser with no [open] support at all still shows one word
   rather than both. */
.tshare-if-open { display: none; }
.tshare-past[open] > summary .tshare-if-shut { display: none; }
.tshare-past[open] > summary .tshare-if-open { display: inline; }
/* .tshare-block.is-past WAS HERE AND IS GONE (update 282). The muting
   moved up to .tshare-branch-group.is-past, because past and upcoming
   blocks are never mixed inside one container - the page splits them
   before it groups them - so a whole container is either finished or it is
   not, and one border beats every row's.

   THE ORDERING AND THE MUTING REMAIN SEPARATE MECHANISMS. The list runs
   newest first inside each container and finished ones sit in their own
   collapsed section; they are there because the view puts them there, not
   because any rule moves them. (271 shipped a comment here insisting the
   muting was the only signal and that the sort ran the other way. Both
   halves were wrong by 273 and the element itself was gone by 282, which
   is a fair argument for keeping notes next to what they describe.) */
/* Everything in the section while timeshare is switched off. Faded and
   unusable, NOT hidden: this is configuration the flag promises to keep,
   and hiding it would make preserved settings look deleted.

   The controls inside carry `disabled` as well, and .section-off-note sits
   OUTSIDE this block so the one sentence explaining the fade is not itself
   faded.

   NEITHER THIS NOR THE OPACITY IS A GATE. A disabled input is simply not
   submitted; the three routes under /admin/timeshare are what refuse a
   write, and the three under /visits are what refuse a booking. Never let
   this be the only thing between a switched-off feature and a write - the
   branch editor shipped that way at 232 and stayed wrong until 258. */
.tshare-editors.is-off { opacity: 0.55; }
/* The container, not the row - .tshare-block has had no background of its
   own since 282. A rule left pointing at the row would have kept matching,
   kept applying, and done nothing at all, which is the kind of dead CSS
   that survives for years. */
.tshare-editors.is-off .tshare-branch-group { background: #F7F5F0; }
/* .tshare-off-note WAS HERE AND IS GONE (update 272), into the shared
   .section-off-note at the top of this file along with the branch and rate
   ones. It shipped at 271 carrying the note above it that a third copy was
   deliberate; forty updates of evidence say otherwise, and the reasoning
   is written out where the shared class is defined. */

@media (max-width: 820px) {
  /* THE TWO DATES STAY SIDE BY SIDE ON A PHONE (update 247d), which is
     where this stopped following .closure-row and .lock-row.

     Stacking them was copied from those two rows without asking whether
     it suited. It does not: a date field is ten characters and the pair of
     them fit across any phone with room to spare, so stacking bought
     nothing and spent a whole row - a column of two short boxes with the
     screen empty beside them and a long drop before the age bands.

     calc(50% - 4px) is half the row less half the 8px gap, so the two come
     out exactly equal with the gap between them. max-width has to be
     cleared as well: the desktop rule caps a field at 200px, and on a
     narrow screen that cap is what was leaving the space to the right. */
  .rate-head { gap: 8px; }
  .rate-head .rate-field { flex: 1 1 calc(50% - 4px); min-width: 0; max-width: none; }
  /* A branch NAME is words, so it takes its own full-width line rather
     than half of one. */
  .rate-head .rate-field.rate-field-branch { flex: 1 1 100%; max-width: none; }
  .rate-foot .btn { flex: 1; }
  /* Label and value on ONE line here, instead of stacked. Stacked it was
     two lines and 45-odd pixels for four words, on the axis a phone has
     least of. flex-basis 100% so it takes the line under the dates rather
     than squeezing in beside them. */
  .rate-nights {
    flex: 1 1 100%; flex-direction: row; align-items: baseline; gap: 6px;
  }
  .rate-nights-label { margin-bottom: 0; }
  .rate-nights-value { padding: 0; }

  /* The timeshare row breaks the same way, and the numbers are copied
     from the rules above deliberately rather than shared: the two rows
     hold the same things in the same order, so they should break at the
     same width, and if one of them ever stops holding the same things
     these are the rules that will need to differ.

     calc(50% - 4px) is half the row less half the 8px gap, so the two
     dates come out exactly equal with the gap between them. max-width has
     to be cleared as well, or the 200px desktop cap leaves space to the
     right on a narrow screen. */
  .tshare-head { gap: 8px; }
  .tshare-head .tshare-field { flex: 1 1 calc(50% - 4px); min-width: 0; max-width: none; }
  /* A branch NAME is words, so it takes its own full-width line rather
     than half of one - and here it is the FIRST field on the row, so it
     takes the top line and the two dates pair up beneath it. */
  .tshare-head .tshare-field.tshare-field-branch { flex: 1 1 100%; max-width: none; }
  .tshare-foot .btn { flex: 1; }
  /* Label and value on ONE line, instead of stacked: stacked it was two
     lines and 45-odd pixels for four words, on the axis a phone has least
     of. flex-basis 100% so it takes the line under the dates rather than
     squeezing in beside them. */
  .tshare-nights {
    flex: 1 1 100%; flex-direction: row; align-items: baseline; gap: 6px;
  }
  .tshare-nights-label { margin-bottom: 0; }
  .tshare-nights-value { padding: 0; }
  /* Remove takes its own full-width line under the dates rather than
     squeezing in beside them, the same way the night count does. It is the
     one destructive control on the row and a phone-width button crammed
     against a date field is a mis-tap waiting to happen. */
  .tshare-head .tshare-remove { flex: 1 1 100%; margin-top: 4px; }
  /* THE LABELS COME BACK ON EVERY ROW. A column heading needs columns, and
     at this width the fields have wrapped into pairs and stacks - the row
     above is no longer above the same thing. Same reveal the receipts
     editor does at 640px, and for the same reason.
     ~
     Only the properties the .sr-only clip set are undone, so each label
     goes back to whatever .tshare-field label and .tshare-nights-label say
     it should look like rather than being restyled here twice. */
  .tshare-block + .tshare-block .tshare-field label,
  .tshare-block + .tshare-block .tshare-nights-label {
    position: static; width: auto; height: auto;
    margin: 0; overflow: visible; clip: auto;
  }
  .tshare-block + .tshare-block .tshare-field label { margin-bottom: 3px; }
  /* Less padding on a phone: the container's own inset costs width the
     dates want, and the border alone still ties the group together. */
  .tshare-branch-group { padding: 10px; }
  /* Tighter above and below the band panel too, for the same reason: the
     desktop 14px is generous on a screen this shape. */
  .rate-bandlist { margin: 10px 0; }
  /* The single caption line stops lining up once the button drops to its
     own row, so it goes and each field gets its own label back.
     .field-label is display:none globally and un-hidden here, which is the
     same mechanism .roomedit-row and .person-row use. */
  .rate-band-head { display: none; }
  /* FROM AGE AND RATE STAY SIDE BY SIDE (update 247e), the same call the
     two date fields got at 247d and for the same reason: an age is two
     characters and a rate is five, so a row each spent the axis a phone
     has least of on fields that did not want it.
     ~
     88px is enough for three digits and the padding; the rate takes
     whatever is left. minmax(0, 1fr) and not a bare 1fr, whose auto
     minimum would let the amount field push the grid wider than the card
     - the rule every mobile grid on this site follows.
     ~
     The button spans BOTH columns on a row of its own. It is the only
     thing in the row that wants width, and squeezing it into a third
     column here is what made the fields narrow in the first place. */
  .rate-band { grid-template-columns: 88px minmax(0, 1fr); }
  .rate-band-actions { grid-column: 1 / -1; }
  /* Repeated inside the query because the rule above it here sets
     display:grid again at this breakpoint, and a hidden row must stay
     hidden on a phone as much as anywhere else. */
  .rate-band[hidden] { display: none; }
  .rate-band-add .btn { width: 100%; }
  .rate-band .field-label { display: block; font-size: 11px; margin-bottom: 3px; }
  .rate-band-actions .btn { width: 100%; }
}

/* ---- Disabled branches (update 233) ---- */
/* Retired, not gone. Muted rather than struck through or greyed to the
   point of unreadable: the row is still fully editable, and the Enable
   button beside it has to look pressable. The tag is what says so; the
   background just stops it reading as one of the live ones. */
.branch-row.is-disabled { background: #F4F2ED; border-style: dashed; }

/* The heading over the disabled group (update 234), replacing the pill
   each row used to carry. Same typography as .branch-head-row, the column
   headings above the list, so the two read as the same kind of label -
   but NOT the same rule, because it must not inherit that one's grid.
   It is an <h4> rather than a styled div so the grouping survives for a
   screen reader: with the pills gone, this heading is the only thing
   saying which rows are disabled.

   AND IT MUST NOT BE HIDDEN ON MOBILE. .branch-head-row is display:none
   under 820px because every field carries its own label in the stacked
   layout, so the column headings are redundant there. This one is not
   redundant anywhere - hide it and a phone shows no sign at all that a
   branch is disabled. */
.branch-group-head {
  margin: 14px 0 0; padding: 0 12px 6px;
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--ink-soft);
}

@media (max-width: 820px) {
  .branch-head-row { display: none; }
  .branch-row {
    display: flex; flex-direction: column; align-items: stretch; gap: 8px;
  }
  .branch-delete-btn { align-self: flex-start; width: auto; padding: 8px 16px; }
}

/* ---- Branch and nights, on the profile page ---- */
/* My details puts the form on the left and the branch in the corner the
   form was leaving empty. Below 800px they stack, form first. */
.details-split {
  display: grid; grid-template-columns: minmax(0, 1fr) 240px;
  gap: 22px; align-items: start;
}
/* Branches switched off: the panel is not rendered at all, so the fixed
   240px track has to go with it or the form sits beside a column of
   nothing (update 232). A one-column grid rather than reverting to a plain
   block, so the gap and align-items keep meaning what they meant. */
.details-split.no-panel { grid-template-columns: minmax(0, 1fr); }
.branch-panel {
  border: 1px solid var(--line); border-radius: 8px;
  background: #FCFBF8; padding: 14px 16px;
}
.branch-panel h3 { margin: 0 0 8px; font-size: 13px; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--ink-soft); }
@media (max-width: 800px) {
  .details-split { grid-template-columns: 1fr; gap: 18px; }
}

/* Three fields reading as one sentence: who / is the / of */
.grid-3 {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px; margin-bottom: 18px;
}
.grid-3 .field { margin-bottom: 0; }
@media (max-width: 640px) { .grid-3 { grid-template-columns: 1fr; } }

/* ---- Editable room rows (admin) ---- */
/* NOTE THE NAME. These are roomedit-*, not room-*, because .room-list and
   .room-row were already taken by the Room details card on the Calendar
   page. Update 94 reused both names and the two rule sets collided: this
   grid overrode the calendar's stacked list, and the calendar's
   ".room-row:last-child { border-bottom: none }" - more specific than a
   plain "border" shorthand - stripped the bottom border off the LAST block
   here. That is a nasty one to spot, because everything looks right except
   one edge of one block on one page. Grep before reusing a class name.

   A block per room rather than a table, so it restacks on a phone instead
   of forcing a sideways scroll. Five fields fit on one line at desktop
   width - unlike the people rows, which needed two - so this is a single
   row of five columns, in source order, with no grid-template-areas.
   Each field carries its own label because there is no shared header row.
   The labels here are real <label for=...> elements rather than the spans
   the people rows use; same appearance, but the checkbox in particular
   needs one to be operable by its label. */
.roomedit-list { display: flex; flex-direction: column; gap: 8px; }
/* ONE layout, used by BOTH halves of the Rooms card (update 219): the Add
   a room form and every row of the editor below it. That is the point -
   the two read as the same six fields in the same places, so the form is
   a blank version of the thing it creates rather than a differently
   arranged relative of it. Change the shape here and both move together;
   changing one alone is what let them drift apart in the first place.

       name    name  colour  owner
       sleeps  beds  beds    double

   THE TWO ROWS SPLIT DIFFERENTLY BUT END IN THE SAME PLACE (update 224).
   Name + Colour occupy tracks 1-3, and so do Sleeps + Bed description, so
   the left block is one rectangle with a different internal division on
   each line. That is the only reason there are four tracks rather than
   three: neither row needs all four boundaries, but between them they do.

     - track 1 (92px)  Sleeps alone. A two-digit number wants no more.
     - track 2 (193px) carries the bulk of Name and of Bed description.
     - track 3 (70px)  the colour swatch, which is 46px plus its label.
                       Bed description absorbs it on the second row.
     - track 4 (116px) both checkboxes, one above the other.

   TRACK 2 IS CAPPED RATHER THAN ELASTIC (update 225). It was 1fr, which
   made it swallow every pixel the card had spare - Name came out 584px and
   Bed description 562px for content that is never more than a few words.
   193px halves both (Name 294, Bed description 272) and leaves the form
   ending well short of the card's right edge, which is intended.

   It is minmax(0, 193px) and not a bare 193px so the track can still
   SHRINK. The whole grid needs 507px, and the card is about 534px wide
   just before the 820px breakpoint - close enough that a bare fixed track
   would be one padding change away from overflowing.

   Moving an area without checking the pair totals still match is the way
   this comes apart - the alignment is the point of the layout, and nothing
   enforces it.

   Six fields in four tracks over two lines. If a seventh is ever added,
   move a line rather than widening this - update 71 added a column to a
   row with no width left and the overflow went unnoticed for weeks.

   EVERYTHING HERE IS THE DESKTOP LAYOUT ONLY. Below 820px the same six
   areas are re-laid over two columns and five lines in a different order
   - see the media query further down. A field added here needs placing
   there too, or it lands wherever the grid's auto-placement puts it. */
.room-grid {
  display: grid;
  grid-template-columns: 92px minmax(0, 193px) 70px 116px;
  grid-template-areas:
    "name   name colour owner"
    "sleeps beds beds   double";
  gap: 10px 12px;
  align-items: start;
}
/* The editor rows are the same grid in a box. The Add form is the same
   grid with nothing around it. */
.roomedit-row {
  padding: 12px;
  border: 1px solid var(--line); border-radius: 8px; background: #FCFBF8;
}
.roomedit-field { min-width: 0; }
.roomedit-name   { grid-area: name; }
.roomedit-sleeps { grid-area: sleeps; }
.roomedit-beds   { grid-area: beds; }
.roomedit-colour { grid-area: colour; }
.roomedit-double { grid-area: double; }
.roomedit-owner  { grid-area: owner; }
.roomedit-field input[type="text"], .roomedit-field input[type="number"] {
  padding: 7px 9px; font-size: 13px;
}
/* Beats the global .field-label { display: none } - see the stay rows. */
.roomedit-row .field-label {
  display: block; font-size: 11px; font-weight: 600;
  color: var(--ink-soft); margin-bottom: 3px;
}
/* The checkbox and the read-only Yes/No have no input box around them, so
   they need nudging down to sit level with the fields either side. */
.roomedit-check input[type="checkbox"] { width: 17px; height: 17px; margin: 7px 0 0; }
.roomedit-static { display: block; font-size: 13px; padding-top: 8px; }
/* Who owns this room, BESIDE the checkbox (update 230). It was on its own
   line underneath.

   The checkbox and the name sit in .roomedit-checkline, a flex row, rather
   than being left to flow inline: the checkbox carries a 7px top margin to
   line it up with the input boxes either side of it, and inline flow would
   have hung the text off that offset instead of centring it against the
   box. The margin therefore moves to the ROW, and the box inside it goes
   back to zero. */
.roomedit-checkline { display: flex; align-items: center; gap: 13px; margin-top: 7px; }
/* flex: none IS WHY THE BOX STAYS 17px (update 319d).
   ~
   THE SHRINKING WAS THE BUG, and it only showed on rooms that HAD an
   owner - which made it look like naming the owner cost the checkbox its
   size, rather than the checkbox simply being a flex item. A flex item's
   default is flex-shrink: 1, and shrinkage is shared out in proportion to
   each item's base size when the row is over its 116px column. The name is
   much the wider of the two, so it took most of it - but the box took the
   rest, and 17px has no room to give. Rooms with no owner had nothing to
   shrink against and stayed correct, which is what made this look like a
   feature.
   ~
   Same fault as .person-action-btn at 310b, in a different shirt: a fixed
   width on a flex item is a suggestion until flex: none makes it a fact.
   ~
   THE NAME ABSORBS ALL OF IT NOW, so it wraps within what is left of the
   column instead. overflow-wrap on .roomedit-note is what keeps a single
   long name inside the card rather than over the field beside it, now that
   the box will not give up its width to make space. */
.roomedit-checkline input[type="checkbox"] { margin: 0; flex: none; }
.roomedit-note {
  display: block; min-width: 0;
  font-size: 11px; line-height: 1.3; color: var(--ink-soft);
  overflow-wrap: anywhere;
}

@media (max-width: 820px) {
  /* MOBILE HAS ITS OWN FIELD ORDER (update 288).

     It was a flex column until then, which threw grid-template-areas away
     and left the fields falling back to DOM order - Name, Colour, Owner
     room, Sleeps, Beds description, Double bed. That dropped a 54px
     colour square in second place, pushed the two fields anyone actually
     edits below it, and gave each checkbox a full-width row of its own.
     Stacked, the phone wants Name, Sleeps, Beds description, the two
     checkboxes together, then the swatch.

     So it stays a grid and gets a SECOND set of areas rather than being
     reordered with flex `order`: the two checkboxes have to share a line,
     which one flex column cannot do without wrapping them in an element
     the desktop grid would then have to see through. The grid-area on
     each child is already declared and was simply being ignored here -
     this makes it count again, which is also why nothing outside this
     block changes.

     TWO COLUMNS, AND ONLY THE CHECKBOX ROW USES THEM. Every other area
     spans both, so the split is sized for that one row and nothing else.
     `auto` gives Double bed exactly its label's width and hands the rest
     to Owner room, which needs it - .roomedit-checkline sets the owner's
     name beside the box, and an even split wraps two names over three
     lines on a 360px screen.

     gap and align-items are deliberately not restated: the base rule's
     10px/12px and `start` are both right here, and `start` is what keeps
     the two checkbox labels level when the owner's name wraps.

     DOM ORDER IS UNCHANGED and no longer matches what is on screen at
     this width - tabbing still goes Name, Colour, Owner room, Sleeps,
     Beds description, Double bed. That is the desktop reading order, and
     it is left alone on purpose: reordering the DOM would not remove the
     mismatch, only move it onto the laptop, where a keyboard is far more
     likely to be in use. The six fields are independent of each other, so
     neither order changes what the form means. */
  .room-grid {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "name   name"
      "sleeps sleeps"
      "beds   beds"
      "owner  double"
      "colour colour";
  }
  .roomedit-check input[type="checkbox"] { margin-top: 0; }
  /* Same reason as the line above. The 7px offset exists to drop a
     checkbox level with the input boxes either side of it on the desktop
     row; here the only thing beside either checkbox is the other one. */
  .roomedit-checkline { margin-top: 0; }
  .roomedit-static { padding-top: 0; }
}

.link-remove-btn { padding: 4px 10px; font-size: 12px; }

/* ---- Both family link tables on a phone ---- */
/* Three columns of names plus a button will not fit 360px at the default
   13.5px with 8px padding. Rather than restack the rows into pseudo-cards
   - which would drop the table semantics assistive technology relies on -
   the text shrinks, the padding tightens and long names are allowed to
   break mid-word. A table is still the right shape for this data; it just
   needs less room. */
@media (max-width: 640px) {
  table.link-table { font-size: 12px; }
  table.link-table th, table.link-table td { padding: 6px 4px; }
  table.link-table th { font-size: 10.5px; letter-spacing: 0; }
  table.link-table .link-remove-btn { padding: 4px 7px; font-size: 11px; }

  table.nights-table { font-size: 12px; }
  table.nights-table th, table.nights-table td { padding: 6px 4px; }
  table.nights-table th { font-size: 10.5px; letter-spacing: 0; }
}

/* A single very long name would otherwise set the table's minimum width on
   its own, and no amount of shrinking would help. */
table.plain td, table.plain th { overflow-wrap: anywhere; }

.branch-line { margin: 0; display: flex; flex-direction: column; gap: 4px; }
.branch-name {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 20px; font-weight: 600; color: var(--pine-dark);
}
/* Numbers stay on one line and line up under each other. The HEADINGS are
   deliberately allowed to wrap on the NARROW table - "Nights so far" on
   one line is most of a phone's width by itself, and wrapping it to two
   lines is what lets the four-column table fit rather than scroll.

   THE WIDE ONE IS THE OPPOSITE CASE, and see .is-wide below. */
table.nights-table td.num, table.nights-table th.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
table.nights-table td.num {
  white-space: nowrap;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
}

/* ---- The branch-wide nights table on My profile (update 210) ---- */
/* One rule between family units, and that is the whole grouping mechanism:
   the rows between two rules are one household. No indent, no sub-heading,
   no extra column - the table is still the four columns it always was, and
   a separator is enough to read the blocks off.

   2px against the 1px row borders, and border-collapse means the thicker
   of the two wins rather than the two stacking. The unit that starts the
   table gets no rule, so the header's own line isn't doubled. */
table.nights-table tr.group-start > td { border-top: 2px solid var(--brass); }

/* The branch total sits in <tfoot> in BOTH layouts, so it is separated and
   weighted the same way without needing a class of its own. The label is
   already <strong>; this is what puts the numbers with it, which is the
   whole reason the total is in a tfoot rather than a tbody row. */
table.nights-table tfoot td {
  border-top: 2px solid var(--brass);
  border-bottom: none;
  font-weight: 600;
}

/* "booked by Ann", "not in your branch" - a quiet note after the name
   rather than a fifth column. Inline, not a block: it shares the cell with
   the name and should sit on the same line where there is room for it. */
.row-meta { margin-left: 8px; color: var(--ink-soft); font-size: 12px; }
/* The note under a cost - "age not known", "3 nights with no rate set".
   .row-meta gives it the colour and size; this puts it on its own line
   under the amount rather than beside it, because the cost column is
   right-aligned and a note trailing a number would push the number out of
   its column and stop the amounts lining up. Its left margin goes for the
   same reason. */
/* TWO CLASSES IN THE SELECTOR, and that is a fix rather than a flourish
   (update 256). `.cost-note` alone is one class, exactly like the
   `.row-meta` rule inside the 640px media query below - so at phone widths
   the later rule won on equal specificity and put 6px back on a note whose
   whole purpose is to start flush under the amount it qualifies. It was
   wrong on every phone since 248 and invisible on every desktop.
   Qualifying with .row-meta puts it out of reach of anything that selects
   on one class. */
.row-meta.cost-note { display: block; margin-left: 0; margin-top: 2px; line-height: 1.3; }
/* "booked by Ann", under the name instead of trailing the Guest pill
   (update 256). Only on GUEST rows - the other thing .row-meta carries is
   "not in your branch", which appears once or twice on a table and reads
   fine inline.

   It is about WIDTH, not taste. On a branch head's table every guest row
   carried a name, a pill and a booker on one line, which set the Who
   column's natural width for the whole table - and since 254 that table
   can be seven columns. Wrapping the booker under the name takes the
   pressure off every other column at once.

   Same two-class selector as .cost-note above, for the same reason: the
   media query below sets margin-left on .row-meta and would otherwise
   out-run a single-class rule at phone widths, which is precisely where
   the space matters most. */
.row-meta.row-meta-below { display: block; margin-left: 0; margin-top: 2px; line-height: 1.3; }
/* The asterisk against an estimated amount. Muted, so it marks the number
   without competing with it, and inline so it stays inside the
   right-aligned cost column instead of pushing the amount out of line. */
.cost-mark { color: var(--ink-soft); margin-left: 1px; }
/* The one line explaining every asterisk in the tables above it. Sits
   under the last table in the card, close enough to read as part of it. */
.cost-footnote { margin-top: 10px; }
@media (max-width: 640px) {
  .row-meta { font-size: 11px; margin-left: 6px; }
}

/* ---- Receipts against the nights (update 254) ---- */
/* Nights in the house goes from five columns to SEVEN when a house
   deducts receipts - three of them money, and money cells are nowrap
   monospace so none of them can give ground. That does not fit a phone and
   was never going to.

   THE SCROLL ALREADY EXISTED. `table.plain` becomes display:block with
   overflow-x:auto below 820px, which has carried the wide tables on this
   site since long before this. What is new is the min-width: without it a
   block-level table shrink-wraps to its container first and only scrolls
   once the nowrap cells force it, which on a 360px screen means the Who
   column collapses to one word per line and the table technically fits.
   The floor makes it use the scroll it already has instead of squashing
   into it.

   540px is arithmetic rather than taste: four number columns that cannot
   wrap, at the 640px-and-below sizing of 12px text with 4px padding, come
   to roughly 400px between them, and the names need the rest to stay
   readable. Recalculate it if that sizing changes - the two are a pair.

   ON THE .is-wide CLASS RATHER THAN .nights-table. The five-column table
   fits a phone and has done since 248; putting a floor under every nights
   table would introduce a scrollbar on the tables that do not need one.

   THE MIN-WIDTH AND THE SCROLL MUST BE ON DIFFERENT ELEMENTS, and putting
   them on the same one is the bug 267 fixed. `table.plain` becomes
   display:block with overflow-x:auto below 820px, so the TABLE is the
   scroll container - and a min-width on a scroll container sizes the
   CONTAINER, not its contents. The table simply became 540px wide and
   overflowed the card and the page. It could not scroll: there was nothing
   left for it to scroll inside.

   So .nights-scroll wraps it. The wrapper is the container and stays as
   wide as the card; the table inside carries the floor and is allowed to
   be wider than its parent, which is exactly the state overflow-x:auto
   exists to handle. */
/* HEADINGS DO NOT WRAP OR BREAK ON THE WIDE TABLE (update 268). Wrapping
   is what lets the narrow table fit a phone, and it is exactly wrong here:
   this one scrolls by design, so there is nothing to be gained by
   squeezing a heading and something to lose - "Receipts" was being broken
   mid-word into "Receipt / s" by the table.plain overflow-wrap:anywhere
   rule, which exists to stop one long NAME setting the table's width and
   has no business touching a heading.

   overflow-wrap AND white-space, because they answer different questions:
   the first is whether a word may be broken, the second whether a line may
   wrap between words. "Receipts" needed the first, "Nights so far" the
   second.

   The table now sizes itself to its content and the min-width below is
   only a floor for the sparse case. That is the right way round for a
   table that scrolls. */
.nights-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table.nights-table.is-wide th {
  overflow-wrap: normal;
  white-space: nowrap;
}
@media (max-width: 820px) {
  table.nights-table.is-wide { min-width: 540px; }
  /* Back to a real table inside the wrapper. The global rule above turned
     it into a block, and a block-level table shrink-wraps its content
     instead of laying columns out across a width - so the column
     alignment the .num rules depend on quietly stops applying. The wrapper
     does the scrolling now, so the table has no need to. */
  .nights-scroll > table.plain { display: table; overflow-x: visible; }
}

/* Owed, when receipts came to more than the nights cost. The house owes
   THEM, and the minus sign in the markup is what says so - this only tones
   it, in the same green the rest of the site uses for the affirmative
   rather than in a red that would read as an error. Somebody being owed
   money is not a fault condition.

   NOT `color: red` and not a badge. The number is already the unusual one
   on the page by virtue of its sign; two more signals would be shouting. */
.owed-credit { color: var(--pine); }

/* ---- The Receipts section on My profile (update 254) ---- */
/* The Add form's three fields. Description takes the slack because it is
   the only one whose length varies - a date input is a fixed width and an
   amount is at most seven digits and a point.

   minmax(0, ...) on all three tracks, which is the rule every grid on this
   site follows: a grid track's default min-width is auto, so one long
   unbroken word in any of them would push the row wider than its card and
   take the page's whole layout with it. */
/* The three fields are placed by POSITION, not by a class each. Date,
   description, value - in that order in the markup, and the tracks below
   are in the same order. Three classes that only said "I am the second
   one" would be three more names in a global namespace, and this file's
   rule is that a class name earns its place. */
.receipt-fields {
  display: grid;
  grid-template-columns: minmax(0, 170px) minmax(0, 1fr) minmax(0, 150px);
  gap: 12px;
  margin-bottom: 14px;
}
.receipt-fields .field { margin-bottom: 0; }

/* THE AMOUNT FIELD HAS TO MATCH THE HEIGHT OF THE TWO BESIDE IT, and left
   to itself it does not. .rate-amount is reused from the Nightly rates
   editor, where every field on the row is one of these, so its inner input
   carries 7px of vertical padding at 13px - a shade tighter than the 9px
   at 14px the global `input` rule gives a plain field. On this section the
   amount sits next to a date input and a text input that DO take the
   global rule, so the borrowed control came out about five pixels short
   and the row read as sagging in the middle.

   Restated here rather than changed at source: the rate editor's rows are
   correct as they are, and its own fields would all grow by five pixels if
   .rate-amount were altered. The borrowing is what needs adjusting, not
   the thing borrowed.

   Both selectors, because the same control appears twice on this section -
   once in the Add form and once per row of the table - and only one of
   them was visibly wrong. Fixing the visible one alone would leave the
   other to be found later. */
.receipt-fields .rate-amount input,
.receipt-table .rate-amount input { padding: 9px 0; font-size: 14px; }

/* The Add form off the table below it. 22px, matching the Add link form on
   this same page, which is the nearest thing to it: a short form with its
   own button, sitting directly above the list it adds to. */
.receipt-add { margin-bottom: 22px; }
/* One column below the point where the description has stopped being wide
   enough to be worth its own track. Same breakpoint .grid-3 uses, and
   deliberately the same: they are the same shape doing the same job. */
@media (max-width: 640px) {
  .receipt-fields { grid-template-columns: minmax(0, 1fr); }
}

/* The list. Every row is a live form - see the note in views.ts for why
   the form elements sit below the table and the inputs point at them with
   form= - so the cells hold controls rather than text, and the padding
   comes down to stop the rows reading twice as tall as the nights table
   above them. */
.receipt-table td { padding: 7px 8px; vertical-align: middle; }
/* The row forms carry no visible content of their own. Without this each
   one contributes a blank line under the table, and with four receipts
   that is four. */
.receipt-table + form { margin: 0; }
.receipt-table input[type="date"],
.receipt-table input[type="text"] { width: 100%; min-width: 0; }
/* The amount keeps its symbol-and-field wrapper from the rate editor -
   .rate-amount, reused rather than copied - and FILLS ITS CELL like the
   date and description fields beside it.

   It briefly did not: it was capped at 130px and pushed right with
   margin-left:auto, on the reasoning that a money column is a .num column.
   That reasoning is right for the nights table, whose money cells hold
   NUMBERS, and wrong here, where the cell holds a text input. A field
   narrower than its column, floated to the far edge while the two beside
   it fill theirs, reads as a misalignment - so the cap and the float are
   gone and the .num class went with them. */
.receipt-table .rate-amount { width: 100%; }
/* Save and Remove, sized and spaced exactly as the pair on House closures.
   The two do the same job on the same kind of row - one form, one Save,
   one destructive Remove - and there is no reason for them to be a
   different size here.

   Deliberately NOT a shared selector with .closure-actions. They should
   look the same today; a shared rule would make "keep them the same" the
   default the day one of them should not, which is the argument this file
   makes for .receipt-table tfoot below.

   nowrap so the pair stays on one line and widens the table instead,
   which table.plain already handles by scrolling sideways below 820px.
   No shrinking on a phone: matching closures means matching them at every
   width, and the scroll is there to be used. */
.receipt-actions { white-space: nowrap; }
.receipt-actions .btn { padding: 8px 14px; font-size: 13px; }
.receipt-actions .btn + .btn { margin-left: 6px; }
/* ---- The receipts editor on a phone (update 268) ---- */
/* Below 640px each row STOPS BEING A ROW and becomes a small stacked
   block: date, then description, then value, then the two buttons, each
   under its own visible label.

   FOUR COLUMNS OF FORM CONTROLS DO NOT FIT 360px AND NEVER WILL. A date
   input alone wants about 120px, and Save and Remove side by side want
   about 150. Left as a table it either squashed every field to
   uselessness or scrolled sideways - and horizontally scrolling a FORM is
   worse than scrolling a table, because you have to scroll back and forth
   between fields to fill in one record.

   THIS IS A DEPARTURE FROM THE RULE THAT TABLES ARE NOT RESTACKED, and the
   distinction is worth keeping: that rule is about DATA tables, where the
   columns carry meaning and losing the table semantics loses the reading.
   This is an EDITOR - a list of records each with its own form - and the
   nearest thing to it in Roomee is House closures on Manage house, which
   has never been a table at all. It is divs stacked exactly like this.
   Nights in the house, one card above, stays a table and scrolls.

   THE LABELS WERE ALREADY THERE. Every field in these rows carries a
   .sr-only label for screen readers; on a phone they are simply made
   visible. No markup changed, and the labels cannot drift out of step with
   the headings because they ARE the headings for anyone not looking at
   the screen. */
@media (max-width: 640px) {
  .receipt-table td { padding: 6px 4px; }

  /* The head row goes: its three words are now printed against each field
     instead. Kept in the DOM rather than removed from the markup, so the
     table is still a table to anything that reads structure. */
  .receipt-table thead { display: none; }

  .receipt-table, .receipt-table tbody, .receipt-table tfoot,
  .receipt-table tr, .receipt-table td { display: block; width: auto; }

  .receipt-table tr {
    padding: 12px;
    margin-bottom: 10px;
    border: 1px solid var(--line); border-radius: 8px; background: #FCFBF8;
  }
  /* The cells lose the borders that drew the grid - inside a card each one
     would be a stray line across the block. */
  .receipt-table td { border-bottom: none; padding: 0 0 10px; }
  .receipt-table td:last-child { padding-bottom: 0; }

  /* The reveal. Same shape as the .field-label used elsewhere on the site,
     so a stacked receipt reads like every other stacked form in Roomee. */
  .receipt-table .sr-only {
    position: static; width: auto; height: auto;
    margin: 0 0 4px; overflow: visible; clip: auto;
    display: block;
    font-size: 11.5px; font-weight: 600; color: var(--ink-soft);
    text-transform: uppercase; letter-spacing: 0.03em;
  }

  /* The amount stops being pushed right - there is no column to align to
     any more. */
  .receipt-table .rate-amount { max-width: none; margin-left: 0; }

  /* Both buttons on one line, sharing the width, which is what the
     confirm dialog does at this size and for the same reason. */
  .receipt-actions { display: flex; gap: 8px; white-space: normal; }
  .receipt-actions .btn { flex: 1 1 0; text-align: center; }
  .receipt-actions .btn + .btn { margin-left: 0; }

  /* The total. Its first cell spans two columns in the markup, which means
     nothing once the cells are blocks, so the label and the amount are put
     back on one line here. */
  .receipt-table tfoot tr { display: flex; justify-content: space-between; background: none; border: none; padding: 12px 4px 0; }
  .receipt-table tfoot td { padding: 0; border-top: none; }
  .receipt-table tfoot td:empty { display: none; }
  .receipt-total-value { padding-left: 0; }
}
/* The total row, matching the one on the nights table above it so the two
   read as the same kind of sum. Shares nothing with it in the stylesheet:
   .nights-table tfoot is scoped to that table, and a shared selector would
   make "keep them looking the same" the default the day one of them should
   not. */
.receipt-table tfoot td {
  border-top: 2px solid var(--brass);
  border-bottom: none;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
/* THE ARITHMETIC IS THE POINT OF THIS RULE. The amounts above this cell
   are inside .rate-amount, which carries 9px of padding of its own inside
   the cell's 8px - so their currency symbols begin 17px from the cell
   edge. A plain total cell begins at 8px and sits visibly left of every
   number it is summing.

   8 + 9 = 17. If either padding changes, this changes with it: the cell
   padding is on .receipt-table td above, the wrapper padding is on
   .rate-amount in the Nightly rates block. Three numbers, one sum, and
   nothing will warn you.

   ABOVE 640px ONLY, in effect. There WAS a 13px version of this in a
   media query here, for the 4px cell padding a phone used to have; 268
   restacks the whole table below 640px, so there is no column left to
   align under and the block above sets this back to 0. The narrow rule was
   REMOVED rather than left, because it sat LATER in the file than the
   restack and would have won on source order - putting a stray 13px
   indent on the total of a stacked card.

   AND THIS RULE IS FENCED ABOVE 640px FOR THE SAME REASON. Unfenced it is
   one class, exactly like the restack's `.receipt-total-value { padding-
   left: 0 }`, and it sits further down the file - so it would win there
   too and reintroduce the indent it was just cleared of. A min-width fence
   is used rather than a more specific selector because it makes the two
   MUTUALLY EXCLUSIVE: neither can apply where the other does, so no
   future reordering of this file can put them back in conflict. */
@media (min-width: 641px) {
  .receipt-total-value { padding-left: 17px; }
}
/* The line explaining why this total and the Receipts column above it do
   not match. Sits under the table, close enough to read as part of it -
   the same placement and margin .cost-footnote has, for the same reason,
   and a separate class because they explain different things. */
.receipt-scope-note { margin-top: 10px; }

/* app.js focuses whichever banner is on the page after a save, so a screen
   reader announces it. That focus is programmatic and the banner is not in
   the tab order, so an outline on it would be decoration on something
   nobody can tab to - and it would appear on every single save. Removing
   it here costs no keyboard user anything. */
.notice-banner:focus, .conflict-banner:focus { outline: none; }

/* Neutral confirmation banner (the red one is .conflict-banner) */
.notice-banner {
  background: #EDF3EE; border: 1px solid var(--tag-2); color: var(--pine-dark);
  border-radius: 8px; padding: 12px 16px; margin-bottom: 18px; font-size: 13.5px;
}

/* "Whose preferences?" picker on the profile page */
.subject-picker { display: flex; align-items: flex-end; gap: 10px; margin: 0; }

/* ---- First-login wizard ---- */
.wizard { max-width: 640px; margin: 20px auto 0; }
.wizard-steps { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }
.wizard-step {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; font-weight: 600; color: var(--ink-soft);
}
.wizard-step.current { color: var(--pine-dark); }
.wizard-num {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; border-radius: 50%;
  background: var(--line); color: var(--ink-soft);
  font-family: 'IBM Plex Mono', monospace; font-size: 12px;
}
.wizard-step.current .wizard-num { background: var(--pine); color: #fff; }
.wizard-step.done .wizard-num { background: var(--tag-2); color: #fff; }
.wizard-line { flex: 1; height: 1px; background: var(--line); }
@media (max-width: 600px) {
  .wizard-steps { flex-direction: column; align-items: flex-start; gap: 6px; }
  .wizard-line { display: none; }
}

/* ---- Other phone-sized tweaks ---- */
@media (max-width: 820px) {
  .container { padding: 0 16px 40px; }
  .card { padding: 16px; }
  h1 { font-size: 24px; }

  /* Wide tables scroll sideways instead of squashing the page */
  table.plain { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }

  /* Calendar: smaller cells and labels so a week still fits across */
  .cal-grid, .cal-dowrow { gap: 3px; }
  .cal-cell { min-height: 62px; padding: 4px; border-radius: 6px; }
  .cal-tags .tag { font-size: 9px; padding: 2px 5px 2px 4px; }
  /* Shrinks alongside the tags (update 242). The cell is 62px tall here
     against 92px on desktop, and a closure name at full size eats most of
     it on its own. */
  .cal-closure { font-size: 9px; margin-top: 3px; }
  .cal-tags .tag::before { width: 5px; height: 5px; margin-top: 2px; }
  /* Stays a row: it is a month name and one button, and stacking them
     made the sticky bar eat a third of a phone screen. */
  .cal-header { flex-direction: row; align-items: center; gap: 10px; margin-bottom: 8px; }
  .cal-header h1 { font-size: 21px; }
}


/* ---- Family members on the booking form ---- */
.family-list {
  display: flex; flex-direction: column; gap: 2px;
  border: 1px solid var(--line); border-radius: 7px;
  padding: 6px 10px; background: #FCFBF8;
}
.family-item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 2px; margin: 0; cursor: pointer;
  font-size: 14px; font-weight: 400; color: var(--ink);
}
.family-item.family-all { border-bottom: 1px solid var(--line); }
.family-item input { flex-shrink: 0; }
@media (max-width: 820px) {
  .family-item { padding: 12px 2px; }
}

/* "Stay with parents" nested under a child's checkbox */
.family-entry { display: flex; flex-direction: column; }
.family-item.family-sub {
  padding: 2px 2px 8px 30px; font-size: 13px; color: var(--ink-soft);
}
.family-item.family-sub input:disabled + span { opacity: 0.45; }
.family-item.family-sub input:disabled { opacity: 0.45; cursor: not-allowed; }

/* Only shown on phones, where the grid scrolls sideways. Since update 110
   it sits between the room key and the stack of months rather than inside
   a card, because there is no longer one card to put it in. */
.cal-hint { display: none; font-size: 12px; color: var(--ink-soft); margin: 0 0 8px; }

@media (max-width: 600px) {
  /* The sideways scrolling this warns about is set up on .cal-inner. */
  .cal-hint { display: block; }
}

/* Past days: muted so the eye skips to what's still to come */
.cal-cell.past { background: #EFECE3; border-color: #E0DBCE; }
.cal-cell.past .daynum { color: #9A948A; }
.cal-cell.past .tag { opacity: 0.5; filter: saturate(0.55); }
.cal-cell.past.today { border-color: var(--brass); background: var(--today-bg); }

/* ---- House closures on the calendar (update 242) ---- */
/* A night the house is shut to everyone. Dashed border and a flatter fill,
   so it reads as "not part of the calendar this month" rather than as a
   day with something in it.

   ORDER MATTERS AND IS WHY THIS SITS HERE. .cal-cell.past above sets its
   own background, and .cal-cell.closed has exactly the same specificity -
   two classes on one element - so with equal weight the LATER rule wins.
   Being below .past is what makes a closed day in the past still read as
   closed, which is the useful half of the pair: a past day is only
   context, a closure explains something. Moving this block above .past
   silently reverses that, with nothing to warn you.

   .today keeps its border in both cases, below, because "where are we now"
   outranks everything. */
/* THE FILL IS A TRUE NEUTRAL GREY, and that is the whole point of it
   (update 243). At 242 it was #EFEDE6 - a warm off-white a shade from
   .past's #EFECE3 - and the two were nearly indistinguishable, which made
   a closed day look like nothing more than a day that had gone by.

   Roomee's palette is warm throughout: greens, tans, and backgrounds with
   red in them. A grey with NO warmth in it is therefore the one thing on
   this page that cannot be mistaken for any other state, and it is darker
   than every other cell as well, so a closure reads as a solid block
   across the week rather than a slightly different shade of the same
   paper. Do not re-warm it to "match the theme" - not matching is the
   job. */
.cal-cell.closed {
  background: #D5D4D0;
  border-color: #B4B2AC;
  border-style: dashed;
}
.cal-cell.closed .daynum { color: #55534E; }
/* Today, closed: the tan border and its weight win back. Listed after the
   two rules it overrides for the same reason those are ordered - equal
   specificity, so position decides. */
.cal-cell.closed.today { border-color: var(--brass-deep); border-style: solid; }

/* The closure's name inside the cell. NOT a .tag: every tag in the
   calendar is a room and carries a room colour, and the key at the top of
   the page says so. A closure is the opposite of a booking, so it gets a
   flat grey label of its own that cannot be mistaken for one.

   overflow-wrap: anywhere matches what .cal-tags .tag does - a long name
   must wrap inside the column rather than force it wider, which on a
   seven-column grid pushes every other day off the screen. */
.cal-closure {
  display: block;
  margin-top: 4px;
  font-size: 10.5px;
  line-height: 1.25;
  font-weight: 600;
  letter-spacing: 0.01em;
  /* Only ever drawn inside a .cal-cell.closed, so it is toned against
     that grey rather than against the ordinary cell background. */
  color: #45433F;
  overflow-wrap: anywhere;
  min-width: 0;
}

/* ---- Room key on the calendar ---- */
/* One scrollable line at every width rather than wrapping to two or three
   rows: it sits inside the sticky bar, and a bar that changes height as
   the window narrows is a bar that jumps about. */
.cal-key {
  display: flex; flex-wrap: nowrap; align-items: center; gap: 6px;
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  padding: 0 0 9px;
  margin: 0;
  border-bottom: 1px solid var(--line);
}
.cal-key .tag { flex: 0 0 auto; font-size: 11.5px; padding: 3px 9px 3px 6px; }

@media (max-width: 600px) {
  .cal-key { gap: 5px; padding-bottom: 8px; }
  .cal-key .tag { font-size: 11px; }
}

/* ---- The scrolling calendar (update 110) ---- */
/* The header and the room key travel together as one sticky bar. One
   element rather than two means nothing has to know how tall anything
   else is - the version of this that chained three sticky bars off each
   other's heights was a standing invitation to break the layout by
   adding a room. The padding-top is what keeps it off the green header. */
.cal-topbar {
  position: sticky; top: var(--topnav-h); z-index: 5;
  background: var(--bg);
  padding: 5px 0 0;
  margin-bottom: 14px;
}

/* The calendar scrolls inside here rather than down the page. Below 600px
   this is also the sideways scroller, which is why there is only one of
   it: two axes, one container, nothing to keep in step. */
.cal-pane {
  height: var(--calpane-h);
  /* Dropped whole on browsers without dvh, leaving the fixed height - so
     a short window gets a slightly tall pane rather than no pane rule. */
  max-height: calc(100dvh - 210px);
  overflow: auto;
  -webkit-overflow-scrolling: touch;
  /* Reaching the last month shouldn't start scrolling the page behind it. */
  overscroll-behavior: contain;
}
.cal-inner { min-width: 0; }

/* One weekday strip for the whole pane, pinned to its top. Sticky resolves
   against .cal-pane because that is the nearest scrollport, which is the
   entire reason the strip lives inside the pane rather than above it. */
.cal-dowrow {
  position: sticky; top: 0; z-index: 3;
  background: var(--card);
  padding-bottom: 6px;
  border-bottom: 1px solid var(--line);
}

/* Each month is its own block with its own divider, which is what makes
   the boundary unambiguous: a week straddling the 31st appears at the end
   of one month and again at the start of the next, rather than being one
   row that belongs to both. The big heading above the pane names whichever
   month is currently at the top; this is the quieter marker in the flow. */
.cal-month { display: block; margin: 0; }
.cal-monthhead {
  margin: 22px 0 10px; padding: 0 0 6px;
  border-bottom: 1px solid var(--line);
  font-family: 'IBM Plex Mono', monospace;
  font-size: 12px; font-weight: 600; letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--ink-soft);
}

/* A month with nothing booked collapses to its divider. <details> does the
   work, so it opens without JavaScript and is keyboard-operable for free.
   display:block rather than flex on the summary is deliberate - flex on a
   summary has a history of breaking the toggle in Safari. */
.cal-month-empty > .cal-monthhead {
  display: block; cursor: pointer; list-style: none;
}
.cal-month-empty > .cal-monthhead::-webkit-details-marker { display: none; }
/* Its own line, under the month name (update 230). It was float: right,
   which put it on the same line at the far end of the divider.

   It stays INSIDE the <summary> - that is what makes it part of the
   toggle, so clicking the words still opens the month. Moving it out to a
   sibling would have looked identical and quietly stopped working. And the
   summary itself is still display:block, not flex: flex on a <summary> has
   a history of breaking the toggle in Safari, which is why this is done
   with display on the child instead. */
.cal-nothing {
  display: block; margin-top: 4px;
  letter-spacing: 0.02em; text-transform: none;
  font-family: inherit; font-size: 12.5px; font-weight: 400;
}
.cal-nothing::after { content: ' +'; font-family: 'IBM Plex Mono', monospace; }
.cal-month-empty[open] > .cal-monthhead .cal-nothing::after { content: ' \2212'; }
.cal-month-empty:not([open]) > .cal-monthhead { border-bottom-style: dashed; }

/* Earlier / Later months, one at each end of the stack inside the pane */
.cal-ends { margin: 4px 0 0; }
.cal-ends:first-child { margin: 0 0 4px; }

/* Landing on a day or a month via an #anchor clears the pinned weekday
   strip and nothing more, so the week you asked for is the top row rather
   than the second one. Matches the opening position set by the inline
   script in calendarPage(); change one and change the other.

   The inline margin (update 209) is the sideways half of the same idea,
   and it is deliberately NOT the same landing as the script's. Native
   anchor scrolling brings an element to the nearest edge and cannot be
   asked to centre; the script centres today's column, because a calendar
   is read forwards and a Friday pinned hard against the right edge shows
   nothing ahead of it. Intercepting the browser's own anchor scroll to
   make the two agree is not worth what it costs, so the vertical axis has
   three authorities that must agree and the horizontal axis has two that
   deliberately differ. Read as drift, this looks like a bug; it isn't. */
.cal-cell { scroll-margin-top: 34px; scroll-margin-inline: 12px; }
.cal-month { scroll-margin-top: 34px; }

.cal-card { padding: 16px 18px; }

@media (max-width: 820px) {
  .cal-pane { max-height: calc(100dvh - 175px); }
  .cal-cell { scroll-margin-top: 30px; }
  .cal-card { padding: 12px 12px; }
}

@media (max-width: 600px) {
  /* Below this width seven readable columns no longer fit, so the pane
     scrolls sideways as well as down. */
  .cal-inner { min-width: 500px; }
}

/* ---- Editable stays on the dashboard ---- */
/* Column labels appear once in .stay-head on wide screens; each row then
   only carries its inputs. Below 820px the head is dropped and the inline
   .field-label spans take over. */
.stay-head {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) 150px 150px 92px;
  gap: 10px;
  padding: 0 12px 6px;
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--ink-soft);
}


.stay-list { display: flex; flex-direction: column; gap: 10px; }

.stay-row {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) 150px 150px 92px;
  gap: 8px 10px;
  align-items: center;
  padding: 12px;
  border: 1px solid var(--line); border-radius: 8px; background: #FCFBF8;
}
.stay-who {
  grid-column: 1; grid-row: 1 / span 2;
  display: flex; flex-direction: column; gap: 2px; min-width: 0;
}
/* Sits directly below the two date fields. This is a CONTAINER as of
   update 222, not the label itself: a stay can now carry two flags - "Stay
   with parents" and, for a guest, "Requires a double room" - and the old
   rule placed the single label at grid-row 2 directly, so a second one
   would have been positioned into the same cell and drawn on top of it. */
.stay-flags {
  grid-column: 2 / 4; grid-row: 2;
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 18px;
}
.stay-flag {
  display: flex; align-items: center; gap: 8px;
  margin: 0; font-size: 13px; font-weight: 400; color: var(--ink-soft); cursor: pointer;
}
.stay-arrive { grid-column: 2; grid-row: 1; }
.stay-depart { grid-column: 3; grid-row: 1; }
/* Row 1 only, not 1 / span 2. Spanning both rows and centring meant the
   button centred across the dates AND the "stay with parents" line below
   them, which dropped it half a row lower than the date fields it sits
   beside. On row 1 alone its centre lines up with theirs, whether or not
   that row has a pin checkbox under it. */
.stay-cancel { grid-column: 4; grid-row: 1; align-self: center; width: 100%; padding: 9px 0; }
.stay-field input { padding: 7px 9px; font-size: 13px; }
.field-label { display: none; }

@media (max-width: 820px) {
  .stay-head { display: none; }
  .stay-row {
    display: flex; flex-direction: column; align-items: stretch; gap: 8px;
  }
  .field-label {
    display: block; font-size: 11px; font-weight: 600;
    color: var(--ink-soft); margin-bottom: 3px;
  }
  .stay-cancel { align-self: flex-start; }
}

/* ---- Lock a room in across a stay (admin) ---- */
.lock-list { display: flex; flex-direction: column; gap: 10px; }
.lock-row {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px;
  padding: 12px; border: 1px solid var(--line); border-radius: 8px; background: #FCFBF8;
}
.lock-who { flex: 0 0 230px; min-width: 0; display: flex; flex-direction: column; gap: 2px; overflow-wrap: anywhere; }
.lock-room-line { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.lock-form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px; margin: 0; flex: 1 1 auto; min-width: 0; }
.lock-field { flex: 1 1 130px; min-width: 118px; }
.lock-field.lock-room { flex: 1.4 1 170px; min-width: 140px; }
.lock-field label { font-size: 11px; margin-bottom: 3px; }
.lock-field input, .lock-field select { padding: 7px 9px; font-size: 13px; }
.lock-actions { display: flex; gap: 6px; flex: 0 0 auto; }
.lock-actions .btn { padding: 8px 14px; font-size: 13px; }
.lock-badge {
  display: inline-block; background: #EFE7D5; border: 1px solid var(--brass);
  color: var(--brass-deep); border-radius: 4px; padding: 0 6px; font-size: 11.5px; font-weight: 600;
}
@media (max-width: 820px) {
  .lock-row { flex-direction: column; align-items: stretch; }
  .lock-who { flex: 0 0 auto; }
  .lock-form { flex-direction: column; align-items: stretch; }
  .lock-field, .lock-field.lock-room { flex: 1 1 100%; }
  .lock-actions .btn { flex: 1; }
}

/* ---- Manage house: sticky section menu ---- */
.page-layout {
  display: grid;
  grid-template-columns: 178px minmax(0, 1fr);
  gap: 22px;
  align-items: start;
}
.page-nav {
  position: sticky; top: calc(var(--topnav-h) + 16px);
  display: flex; flex-direction: column; gap: 2px;
}
.page-nav a {
  display: block; padding: 8px 12px;
  border-left: 3px solid transparent; border-radius: 0 7px 7px 0;
  text-decoration: none; color: var(--ink-soft);
  font-size: 13.5px; font-weight: 500;
}
.page-nav a:hover { background: rgba(57, 72, 58, 0.06); color: var(--pine-dark); }
.page-nav a.current {
  background: var(--card); border-left-color: var(--brass);
  color: var(--pine-dark); font-weight: 600;
}
/* Only one section on screen at a time, once JS has taken over */
.page-sections.tabbed > .card { display: none; }
.page-sections.tabbed > .card.active { display: block; }
/* A SECTION THAT IS MORE THAN ONE CARD (update 247a).
   ~
   Until now every menu entry pointed at exactly one .card, and the two
   rules above are why: they match DIRECT CHILDREN of .page-sections, so a
   second card in the same section could never be hidden with the first.
   Nightly rates wanted two - the switches and the rate periods are
   different kinds of thing and reading them as one long card was most of
   what made that tab feel cluttered - so a wrapper takes the id and the
   cards sit inside it.
   ~
   The script needs NO change for this. show() finds its sections with
   querySelector on the href and toggles .active on whatever comes back,
   which was never card-specific; only the CSS was. Everything else on the
   site still points a menu entry straight at a card and is untouched. */
/* CURRENTLY UNUSED (update 261). .page-section wraps SEVERAL cards into one
   tab, so a section can be more than one card. Admin -> Nightly rates was
   its only caller, from 247a until 260 moved that tab's switches to General
   settings and left the upper card holding a heading and a sentence; 261
   removed the card and the wrapper with it.

   KEPT ON PURPOSE. It is two lines, it is the general answer to "this tab
   needs two cards", and rediscovering it costs more than carrying it. The
   tab script needs nothing for it either way - it matches on the id,
   wherever that id happens to sit. */
.page-sections.tabbed > .page-section { display: none; }
.page-sections.tabbed > .page-section.active { display: block; }

@media (max-width: 900px) {
  /* minmax(0, 1fr), not 1fr. A bare 1fr is minmax(auto, 1fr), whose auto
     minimum lets the column grow to fit its widest child - so one wide
     table stretches the grid past the viewport and the whole document
     scrolls sideways. That is what makes the header bar stop short: it is
     as wide as the viewport, not as wide as the scrolled page. The desktop
     rule above already guards against this; the mobile one did not. */
  .page-layout { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  .page-sections, .page-sections > .card, .page-sections > .page-section { min-width: 0; }
  .page-nav {
    top: var(--topnav-h); z-index: 4;
    flex-direction: row; overflow-x: auto; -webkit-overflow-scrolling: touch;
    gap: 6px; padding: 8px 0; margin-bottom: 4px;
    background: var(--bg); border-bottom: 1px solid var(--line);
  }
  .page-nav a {
    flex: 0 0 auto; white-space: nowrap;
    border-left: none; border-bottom: 2px solid transparent; border-radius: 6px;
    padding: 7px 11px;
  }
  .page-nav a.current { border-left: none; border-bottom-color: var(--brass); }
}


/* ---- Keep the header in view while scrolling ---- */
.topnav { position: sticky; top: 0; z-index: 20; }

/* The other sticky bits sit below the header rather than under it. They all
   read --topnav-h, which shrinks with the header on small screens. */
.cal-key { top: var(--topnav-h); }
.page-sections .card { scroll-margin-top: calc(var(--topnav-h) + 16px); }
/* A HEADING USED AS AN ANCHOR TARGET NEEDS THE SAME OFFSET (update 311a).
   ~
   The rule above covers every anchor in Roomee that is a CARD inside a
   sectioned page. /account is neither: it is one card holding two
   headings, so the anchor has to sit on the heading itself - and without
   this the browser scrolls "Your plan" to y = 0, which is underneath the
   sticky .topnav. The Upgrade buttons on Settings and the People page both
   land there, and what you see is the middle of the plan cards with no
   heading above them.
   ~
   WRITTEN FOR ANY id'd HEADING, not for #plans. There is exactly one today
   and the next one would hit the same wall for the same reason; a rule
   keyed to one id would be a fix for one page rather than for the
   pattern. */
h2[id], h3[id] { scroll-margin-top: calc(var(--topnav-h) + 16px); }
/* .card carries its own bottom margin, so a wrapper needs no gap of its
   own - two cards inside one section space themselves exactly like two
   sections' worth of card did. */
.page-section { min-width: 0; }

@media (max-width: 820px) {
  /* The stacked pills are 40px here, taller than the 36px logo, so THEY
     set the height: 40px + 12px padding top and bottom + 1px border. */
  :root { --topnav-h: 65px; }
  /* Cells are shorter here (62px, gap 3), so six rows need less room. */
  :root { --calpane-h: 430px; }
}

/* ---- Filter a list by name ---- */
.filter-box { margin-bottom: 12px; max-width: 280px; }
.filter-box input {
  padding: 8px 11px; font-size: 13.5px;
  background: #FCFBF8;
}
/* The rows set their own display, which beats the browser's [hidden]
   rule, so hiding has to be spelled out. */
.stay-row[hidden], .lock-row[hidden], .person-row[hidden] { display: none; }
.filter-empty { padding: 18px 10px; }

/* Calendar month navigation */
.cal-nav { display: flex; gap: 6px; flex-wrap: wrap; }


/* Readable long-form text (How it works) */
.prose { margin-bottom: 16px; }
.prose h2 { margin: 0 0 10px; }
.prose p { line-height: 1.6; margin: 0 0 12px; color: var(--ink); }
.prose p:last-child { margin-bottom: 0; }
.prose ul { margin: 0; padding-left: 20px; }
.prose li { line-height: 1.6; margin-bottom: 9px; }
.prose li:last-child { margin-bottom: 0; }
.prose .hint { margin-top: 12px; }

@media (max-width: 600px) {
  .daycount { font-size: 9.5px; padding: 0 4px; line-height: 14px; }
}

/* ---- Test deployment banner ---- */
/* Sits above the sticky header and scrolls away with the page, so it never
   changes --topnav-h or shifts the things that stick underneath it. */
.test-banner {
  background: var(--danger);
  color: #F6F4ED;
  text-align: center;
  padding: 7px 16px;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

@media (max-width: 600px) {
  .test-banner { font-size: 10.5px; padding: 6px 12px; letter-spacing: 0.04em; }
}

/* ---- Admin: viewing Roomee as someone else (update 98) ---- */
/* Same placement trick as the test banner above: it sits outside .topnav
   and scrolls away, so --topnav-h is untouched and .cal-key, .page-nav and
   .page-sections .card all still stick to the right place. On the test
   deployment the two banners simply stack. */
.viewas-banner {
  background: var(--brass-deep);
  color: #F6F4ED;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 12px;
  padding: 7px 16px;
  font-size: 12.5px;
}
.viewas-banner strong { font-weight: 600; }
.viewas-banner .btn.secondary {
  color: #F6F4ED; border-color: rgba(246, 244, 237, 0.45);
  background: transparent; padding: 4px 12px; font-size: 12px;
}
.viewas-banner .btn.secondary:hover { background: rgba(246, 244, 237, 0.14); }

/* General utility, first needed here: keeps a label in the accessibility
   tree while taking it out of the layout. Lives in this block rather than
   up with the base styles only because appending is safer than splicing
   into the middle of the file. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* The person picker that replaces the name in the header for admins */
.nav-viewas { display: flex; align-items: center; gap: 6px; margin: 0; min-width: 0; }
.nav-viewas select {
  padding: 4px 8px; font-size: 12.5px;
  /* A fixed width, not max-width. A select sizes itself to its widest
     option, so max-width alone made the box as wide as the longest name
     in the family. This is sized to fit "Neil Wiltshire" plus the arrow;
     longer names ellipsis in the closed box and still read in full when
     it's open. */
  width: 134px; min-width: 0;
  text-overflow: ellipsis;
  background: rgba(246, 244, 237, 0.1); color: #F6F4ED;
  border: 1px solid rgba(246, 244, 237, 0.3); border-radius: 6px;
}
/* The options themselves render on the browser's own popup background, so
   they need the dark ink back or they come out white on white. */
.nav-viewas select option { color: var(--ink); background: #FFFFFF; }
.nav-viewas .btn.secondary {
  color: #F6F4ED; border-color: rgba(246, 244, 237, 0.4); background: transparent;
  padding: 5px 11px; font-size: 12px;
}
.nav-viewas .btn.secondary:hover { background: rgba(246, 244, 237, 0.1); }

@media (max-width: 820px) {
  .viewas-banner { font-size: 11.5px; padding: 7px 12px; gap: 9px; }
  /* Inside the burger panel .nav-user is a space-between row; the picker
     needs a line of its own rather than being squeezed next to Sign out. */
  .nav-user { flex-wrap: wrap; }
  .nav-viewas { flex: 1 1 100%; flex-wrap: nowrap; gap: 6px; margin-bottom: 10px; }
  /* width:auto here, or the fixed desktop width beats flex-grow and the
     picker sits narrow in the middle of the burger panel. */
  .nav-viewas select { flex: 1 1 auto; width: auto; padding: 8px 9px; font-size: 14px; }
  .nav-viewas .btn.secondary { padding: 8px 14px !important; font-size: 13px; }
}

/* ---- Two form fields side by side (update 104) ---- */
/* Used by both booking forms. The child .field keeps its own 14px bottom
   margin, so the grid needs a column gap only - a row gap would double up
   the moment this stacks on a phone.

   minmax(0, 1fr) rather than 1fr: a date input has an intrinsic minimum
   width that a bare 1fr will honour, pushing the grid wider than the card
   and scrolling the whole document sideways. Same bug as update 91. */
.field-pair {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 0 16px;
}
/* Two date inputs need roughly 340px between them. Below this the card's
   inner width can no longer supply that, so they stack. */
@media (max-width: 620px) {
  .field-pair { grid-template-columns: minmax(0, 1fr); }
}

/* The timeshare block picker on Book a stay (update 284).

   LINKS STYLED AS CARDS, not buttons and not a <select>. Each one is a
   real navigation - /book?block=<id> - so it works with JavaScript off and
   can be bookmarked, and a select would have needed a script to do
   anything on change, which WCAG 3.2.2 rules out for this codebase
   anyway (the admin view-as picker is the one documented exception).

   Wrapping row rather than a column: a branch usually has two or three
   blocks to choose between, and three short date ranges stacked down the
   page look like a list of records rather than a set of choices. */
.book-blocks { margin: 0 0 16px; }
.book-blocklist { display: flex; flex-wrap: wrap; gap: 8px; }
.book-block {
  display: flex; flex-direction: column; gap: 2px;
  padding: 9px 12px; min-width: 150px;
  border: 1px solid var(--line); border-radius: 8px; background: #FCFBF8;
  text-decoration: none; color: inherit;
}
.book-block:hover { border-color: var(--pine); }
/* The chosen one. Border AND background, not colour alone: a single hue
   shift is the kind of state a person misses on a bright screen, and this
   one decides which fortnight the form below will accept. */
.book-block.is-chosen {
  border-color: var(--pine); border-width: 2px; padding: 8px 11px;
  background: #EDF2EE;
}
.book-block-dates { font-weight: 600; font-size: 13.5px; }
.book-block-nights { font-size: 12px; color: var(--ink-soft); }

@media (max-width: 620px) {
  /* Full width each, since two of these side by side on a phone leaves
     each too narrow for a date range and they wrap mid-word. */
  .book-block { flex: 1 1 100%; min-width: 0; }
}

/* Both booking forms while the person has no branch and the house is
   sharing nights out between them (update 277).

   THE FOURTH .is-off WRAPPER, and unlike the three on the Admin page this
   one has nothing to restyle inside it - there are no rows here, just a
   form - so it is opacity and nothing else. That is also why it is a
   separate selector rather than being added to any of theirs: the reason
   those three are kept apart is that each restyles a differently-named row
   class, and joining a fourth that restyles none of them would mean a
   change to a rate period could reach this page.

   The note stays OUTSIDE this wrapper, exactly as .section-off-note does
   on the Admin cards. It is the one sentence explaining why the rest is
   faded, and fading the explanation along with the thing it explains is
   how a greyed section becomes a mystery.

   NEITHER THIS NOR THE `disabled` ON THE CONTROLS IS A GATE. POST /visits
   and POST /visits/guest are what refuse; a disabled input is merely not
   submitted. See the note on bookOff in views.ts. */
.book-editors.is-off { opacity: 0.55; }

/* ---- Guest bookings (update 103) ---- */
/* Named guest-* rather than reusing anything: .room-list cost an evening
   in the 91-97 run, and .field-label is globally hidden, so the checkbox
   row below carries its text in a span instead. */

.guest-optional {
  font-weight: 400; font-size: 12px; color: var(--ink-soft);
  text-transform: none; letter-spacing: 0;
}

/* A label wrapping its own checkbox: the global `label { display:block }`
   would stack the box above the text, so this one is a flex row.

   .setting-check shared this selector from update 232 until 240, when the
   Use branches box lost its caption to the row label beside it and stopped
   being the same shape. It is now .setting-checkbox, down with the rest of
   the Settings rules, and this is a lone selector again. */
.guest-check {
  display: flex; align-items: center; gap: 10px;
  margin: 0; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: 7px; background: #FCFBF8;
  font-size: 14px; font-weight: 400; color: var(--ink); cursor: pointer;
}
.guest-check input { flex-shrink: 0; width: 17px; height: 17px; }

.guest-badge {
  display: inline-block; margin-left: 8px; padding: 1px 7px;
  border-radius: 10px; background: var(--brass); color: #fff;
  font-size: 11px; font-weight: 600; letter-spacing: 0.02em;
  vertical-align: 2px;
  /* .stay-who is a column flex container, and a flex item is blockified -
     display:inline-block is ignored and the default align-items:stretch
     spreads the pill across the whole cell. This is what keeps it hugging
     the word. Harmless in a table cell, where it simply does nothing. */
  align-self: flex-start;
}
/* .stay-who is a column flex container, so name and pill would each take
   a line of their own. .stay-name wraps them into one flex item; its own
   contents stay inline, which puts the pill to the right of the name and
   lets the 8px margin above do the spacing. Deliberately NOT a nested flex
   row - that would re-blockify the pill and bring the stretching back.

   Also used by .lock-who on Manage house -> Room assignments, which is the
   same column-flex shape. Kept as one class rather than a second identical
   rule: if this ever needs to differ between the two, split it then. */
.stay-name { min-width: 0; }

table.guest-table { margin-top: 10px; }
table.guest-table td { font-size: 13.5px; }
/* Night counts sit in the same monospaced, right-aligned columns as the
   two tables above, so the three read as one set rather than three. */
table.guest-table td.num, table.guest-table th.num {
  text-align: right;
  white-space: nowrap;
}
table.guest-table td.num { font-family: 'IBM Plex Mono', ui-monospace, monospace; }

@media (max-width: 820px) {
  .guest-check { padding: 12px; }
  /* Matches the nights-table treatment, since the columns now match. */
  table.guest-table { font-size: 12px; }
  table.guest-table th, table.guest-table td { padding: 6px 4px; white-space: nowrap; }
}

/* ---- The room colour swatch (update 218) ---- */
/* `<input type="color">` renders as a block of its own value, so the
   element IS the swatch - clicking it opens the browser's own picker.
   Everything here is sizing; the control needs no JavaScript.

   Appended rather than spliced in beside the .roomedit-* rules on purpose.
   Block edits to the middle of this file have twice taken unrelated rules
   with them. */
.colour-input {
  /* The global `input` rule sets width:100%, which would stretch the
     swatch across its whole grid cell. */
  width: 46px; height: 34px;
  padding: 3px;
  border: 1px solid var(--line); border-radius: 8px;
  background: #FCFBF8;
  cursor: pointer;
}
/* Browsers wrap the colour block in their own shadow element, and both
   give it a default border and padding that stop it filling the control.
   These two are the only way to reach inside it; a browser that supports
   neither simply shows its default swatch, which still works. */
.colour-input::-webkit-color-swatch-wrapper { padding: 0; }
.colour-input::-webkit-color-swatch { border: none; border-radius: 5px; }
.colour-input::-moz-color-swatch { border: none; border-radius: 5px; }

.colour-input:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }

@media (max-width: 820px) {
  /* A larger target on a phone. The fixed width is what matters: below
     820px the swatch sits on a line of its own at the foot of the form
     (update 288), spanning both of that grid's columns, and the global
     input width:100% would otherwise run it the full width of the card.
     align-self is inert - the swatch's parent is a plain block - and is
     left only because it costs nothing and reads as intent. */
  .colour-input { width: 54px; height: 40px; align-self: flex-start; }
}

/* ---- Confirmation dialog (update 223) ---- */
/* Replaces the browser's own confirm box on every destructive button. See
   the long note at the top of app.js for the flow - the short version is
   that the dialog is asynchronous where window.confirm was not, so the
   click is always cancelled and then re-dispatched if the answer is yes.

   A native <dialog> opened with showModal(), not a hand-built overlay.
   Focus trapping, Esc, the backdrop, inertness of the page behind and
   correct stacking above every sticky header on the site all come free and
   are all things a div would have had to get right by hand.

   Appended rather than spliced in beside the button rules on purpose.
   Block edits to the middle of this file have twice taken unrelated rules
   with them. */
.confirm-dialog {
  /* The dialog element is only the positioning box; .confirm-panel is what
     is actually seen, so this carries no chrome of its own. */
  border: none; padding: 0; background: transparent;
  width: calc(100% - 32px); max-width: 420px;
  color: var(--ink);
}
.confirm-dialog::backdrop { background: rgba(38, 46, 39, 0.55); }
.confirm-panel {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 22px;
  box-shadow: 0 18px 44px rgba(38, 46, 39, 0.28);
}
.confirm-panel h2 { margin: 0 0 8px; font-size: 18px; }
/* No bottom margin here, and the spacing above the buttons lives on
   .confirm-actions instead. That is what lets the detail line be hidden
   without leaving the heading jammed against the buttons. */
/* pre-line, so a detail written with newlines can break into bullets
   (update 247f). It preserves line breaks and collapses runs of spaces,
   which is exactly the pair wanted here - the string is built in index.ts
   and indented there for readability, and none of that indentation should
   reach the screen.
   ~
   THE MECHANISM WORKS IN BOTH DIALOGS, which is why it is done this way
   rather than by putting a list in the markup. app.js assigns the detail
   with textContent, so newlines survive into the DOM and this makes them
   visible; and the window.confirm fallback, for a browser with no
   <dialog>, honours newlines natively with no help at all. A <ul> would
   have needed innerHTML in one path and would have arrived as tag soup in
   the other.
   ~
   Details written as a single sentence are unaffected: no newlines, no
   change. */
.confirm-detail {
  margin: 0; color: var(--ink-soft); font-size: 13.5px; line-height: 1.5;
  white-space: pre-line;
}

/* The ITEMISED dialog (update 261): one subheading and one paragraph per
   thing a single Save is about to do. Drawn instead of .confirm-detail,
   never alongside it, and only when two warnings are live at once - which
   today means unticking Enable House closures and flipping per-branch
   rates in the same save.

   The subheadings are the warnings' OWN headings, the ones a
   single-warning dialog would put in the h2. That is what makes each
   paragraph attributable: without them 260 produced two questions and two
   consequences running together with nothing marking the join.

   Sized BELOW the dialog's h2 and above the body text, so the eye reads
   panel title -> item -> item rather than treating them as three peers. */
/* PLAIN BLOCK FLOW, NOT A FLEX COLUMN WITH A GAP, and that is not a
   stylistic preference. A gap applies between EVERY pair of children
   equally, so it would push each subheading the same distance from its own
   paragraph as from the next item - which destroys the one thing this
   layout exists to show, that each paragraph belongs to the heading above
   it. Margins can be asymmetric and a gap cannot. */
.confirm-items h3 {
  margin: 0 0 4px;
  font-size: 14px; font-weight: 600; color: var(--ink);
}
.confirm-items p {
  margin: 0; color: var(--ink-soft); font-size: 13.5px; line-height: 1.5;
  white-space: pre-line;
}
/* The space and the rule between items, on the SECOND and later headings
   rather than between siblings. :not(:first-child) gets the same result as
   a sibling combinator and survives the list being reordered or reduced to
   one - with a single item this never fires, which is what stops a lone
   item wearing a stray rule. */
.confirm-items > h3:not(:first-child) {
  margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--line);
}
.confirm-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }

@media (max-width: 600px) {
  /* Full width and equal halves - these are the two hardest buttons on the
     site to hit by accident, and they should stay that way. */
  .confirm-actions { gap: 8px; }
  .confirm-actions .btn { flex: 1 1 0; text-align: center; }
}

/* ---- The house photo dialog (update 314) ---- */
/* Opened by the camera on a picker card. Drop a file on it or click to
   browse, look at the preview, Save.

   IT BORROWS .confirm-dialog's SHELL AND NOT ITS PANEL. The backdrop, the
   width and the "the dialog is only a positioning box" arrangement are
   identical and there is no reason for a second answer to them, so app.js
   puts BOTH classes on this element. What it does not borrow is the
   panel's 420px feel for one short question - this one holds a drop zone
   and a card-sized preview, and it needs the room. */
.photo-dialog { max-width: 380px; }
/* THE DROP ZONE IS A <label> WRAPPING A HIDDEN <input type="file">, which
   is what makes one control answer to a click, the keyboard and a drop
   without any of the three being special-cased. Clicking a label opens the
   file picker for free; focus lands on the input, so :focus-within is what
   draws the focus ring.

   DASHED, LIKE .house-card-add, and for the same reason: it reads as a
   space where something could go rather than as a fourth kind of button.
   The two are the only dashed outlines in Roomee and they mean the same
   thing. */
.photo-drop {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; text-align: center;
  margin-top: 16px; padding: 18px 16px;
  border: 2px dashed #CFC8B6; border-radius: var(--radius);
  background: var(--bg); color: var(--ink-soft); font-size: 13.5px;
  cursor: pointer;
}
.photo-drop:hover, .photo-drop:focus-within { border-color: var(--brass); color: var(--pine-dark); }
/* While a file is being dragged over it. Set by app.js on dragenter and
   cleared on dragleave and drop - the class rather than :hover, because a
   drag does not produce hover states. */
.photo-drop.dragging { border-color: var(--pine); background: var(--today-bg); color: var(--pine-dark); }
.photo-drop strong { font-weight: 600; color: var(--ink); }
/* The input is hidden but MUST STAY FOCUSABLE, so display:none and
   visibility:hidden are both wrong - either would take it out of the tab
   order and leave the drop zone unreachable by keyboard. Clipping it to
   nothing keeps it focusable, which is what makes :focus-within above draw
   a ring somebody can see. */
.photo-drop input[type='file'] {
  position: absolute; width: 1px; height: 1px;
  clip-path: inset(50%); overflow: hidden;
}

/* THE PREVIEW IS THE CARD, AT THE CARD'S SIZE, WITH THE HOUSE'S OWN NAME
   OVER IT. Cropping is accepted on this feature - whatever falls outside
   the card is lost - and that is a far easier thing to accept when you can
   see it before you commit. What is approved here is what appears on the
   picker.

   IT DELIBERATELY MIRRORS .house-card RATHER THAN REUSING IT. Reusing the
   class would drag in cursor:pointer, the hover, the focus ring and the
   current-house treatment, on something that is not a control and cannot
   be pressed - and it would tie the preview to every future change made to
   the card for a different reason.

   THAT DUPLICATION IS THE COST AND IT IS WORTH NAMING: the width, the
   min-height, the radius, the border and the type below are copies. THE
   ONE NUMBER THAT MATTERS IS NOT COPIED - both fades read --photo-fade, so
   the preview cannot drift from the card on the only property that would
   make it lie. If the card's SHAPE changes, change it here too.

   It is not exactly the card and cannot be: a card stretches to the
   tallest in its row, so a two-line house name elsewhere on the page makes
   the real one taller than this. The crop is very slightly generous
   in that case, never mean. */
.photo-preview {
  position: relative;
  width: 200px; min-height: 120px; margin: 16px auto 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  text-align: center;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 18px 20px;
}
.photo-preview::before {
  content: '';
  position: absolute; inset: 0;
  border-radius: inherit;
  background-image: var(--house-photo);
  background-size: cover;
  background-position: var(--house-photo-pos, 50% 50%);
  opacity: var(--photo-fade);
  pointer-events: none;
}
.photo-preview > span { position: relative; z-index: 1; }
.photo-preview .house-card-name { font-size: 20px; }
/* Nothing chosen and nothing stored: the preview draws as an empty card so
   the dialog does not change height when a photo arrives. Without this the
   whole panel jumps as soon as a file is dropped. */
.photo-preview.empty::before { background-image: none; }

/* Refusals from the route - too large, not an image - and anything the
   resize threw. Red, and it takes the place of nothing: it is empty and
   invisible until there is something to say. */
.photo-error {
  margin: 12px 0 0; font-size: 13px; line-height: 1.45; color: var(--danger);
}
/* Remove sits apart from Cancel and Save, because it is the only one of
   the three that acts on what is ALREADY stored rather than on what is
   being chosen. margin-right: auto pushes the other two to the right and
   leaves it at the left, which is where Roomee puts a destructive action
   that is not the point of the dialog. */
.photo-actions .photo-remove { margin-right: auto; }

/* ---- Moving the photo inside the card (315, rebuilt at 315a) ---- */
/* GRAB THE PHOTO AND MOVE IT. 315 shipped a slider instead, on the
   argument that a range input is keyboard-accessible for free where a drag
   is not. That was the wrong trade: it answered a real problem with a
   control nobody has seen before, when the answer was to make the FAMILIAR
   control reachable by keyboard as well. app.js does both - pointer events
   for the drag, arrow keys doing the identical thing.

   THE CLASS IS ADDED ONLY WHEN THERE IS SLACK TO MOVE INTO. A cover crop
   overflows in at most one direction, and a photo at the card's own ratio
   cannot move at all - so a grab cursor on one would be a promise the
   picture cannot keep.

   touch-action: none IS LOAD-BEARING, NOT TIDINESS. Without it a drag
   under a finger scrolls the dialog instead of moving the photo, and the
   browser swallows the pointermove events on the way. It is the single
   line that makes this work on a phone.

   user-select: none stops the house name inside the preview being
   selected, and highlighted blue, halfway through a drag across it.

   THE FOCUS RING IS ON .draggable ONLY, because that is the only state in
   which the preview is a tab stop - see measureOverflow, which adds and
   removes the tabindex with the class. */
.photo-preview.draggable {
  cursor: grab;
  touch-action: none;
  user-select: none; -webkit-user-select: none;
}
.photo-preview.draggable:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }
/* .grabbing, NOT .dragging, deliberately: .photo-drop.dragging already
   exists a few rules above for a file being dragged ONTO the dialog, which
   is a different gesture meaning a different thing. Two states sharing a
   name in one dialog is how the wrong one gets edited. */
.photo-preview.grabbing { cursor: grabbing; }
/* Says the photo can be moved, on a touch screen where there is no cursor
   to say it. Drawn only when it is true. */
.photo-hint {
  margin: 8px 0 0; text-align: center;
  font-size: 12.5px; color: var(--ink-soft);
}

/* ---- Admin -> Settings: the setting left, the explanation right ---- */
/* Two columns. Left: a setting - its label above its control. Right: the
   prose that explains it.

   WHY. Before 239 this was one narrow column, so each setting's
   explanation sat between its own field and the next one: four settings
   read as eight stacked things, and the two long ones (Timezone, Use
   branches) pushed the controls either side of them most of a screen
   apart. 239 moved the prose out here. 240 tried the labels out beside
   their fields in a third column and 241 put them back above, with more
   air between label and field than the 4px they had before - one thing
   from 240 stays, which is that Use branches has a label of its own like
   the other three instead of a caption beside its checkbox.

   THE 1000px BREAKPOINT IS ARITHMETIC, NOT TASTE, AND IT MOVES WHENEVER
   THE LEFT COLUMN DOES. It was 1040 while the label had a column of its
   own at 240. The right-hand column is prose, and prose stops being
   comfortable below about 45 characters, which at 13px is roughly 300px.
   Working back:

     note = (viewport - 40 container padding - 178 page-nav - 22 gap
             - 44 card padding) - 340 setting column - 32 column gap

   which reaches 300px at about a 956px viewport; 1000 leaves some margin.
   Note the page-nav term: above 900px the sidebar is still on screen, so
   the widest viewport that gives a CRAMPED note is 901px, not 821px -
   putting this breakpoint at the 820px used elsewhere in this file would
   leave a band of ~180px where the explanation is 40 characters wide. That
   is the trap in this layout and it is not visible from the markup.

   The left column is a fixed 340px rather than a fraction, so the four
   controls are the same width as each other whatever the card is doing,
   and so the longest timezone label still fits its select without
   truncating. The right is minmax(0, 1fr) and NOT a bare 1fr: a bare 1fr
   is minmax(auto, 1fr), whose auto minimum lets a long unbroken word push
   the column - and the grid - wider than its container. Same rule as
   .page-layout and every mobile grid on the site.

   align-items: start, so the note sits level with the label rather than
   floating in the middle of a tall row. */
.setting-row {
  display: grid;
  grid-template-columns: 340px minmax(0, 1fr);
  column-gap: 32px;
  row-gap: 6px;
  align-items: start;
  padding: 18px 0;
  border-top: 1px solid var(--line);
}
/* The first row butts up against the intro line above it, so it takes no
   border and almost no top padding - the <h2> and its hint already do that
   job. :first-of-type rather than :first-child because the conditional
   blocks in the markup make "first child" a promise this file cannot
   keep. */
.setting-row:first-of-type { border-top: none; padding-top: 4px; }

/* A COLUMN FLEX BOX, AND THE GAP IS THE POINT (update 241). The space
   between a label and its field is 9px here rather than the 4px the global
   `label` rule gives every other form on the site, which is what stops the
   two reading as one lump now that they have a row to themselves.

   Doing it with gap rather than a margin is what keeps this off the Use
   branches checkbox. That box is ALSO a label - it wraps its input so the
   whole thing is clickable - so a `.setting-control label` margin rule
   would land on both, and would out-specify .setting-checkbox into the
   bargain. A gap belongs to the container and cannot pick the wrong child.

   .field is deliberately not reused for the same reason: it carries a
   bottom margin every other form depends on, and the spacing in this one
   is the gap here plus the rows' own padding. */
.setting-control {
  display: flex; flex-direction: column; align-items: stretch;
  gap: 9px; min-width: 0;
}
/* Cleared so the gap above is the only thing setting this distance. Left
   in place, the global 4px would ADD to it and the four rows would each be
   13px, which is a number nobody chose. */
.setting-control > label { margin-bottom: 0; }

/* The Use branches checkbox (update 240, replacing .setting-check from
   232). It used to be a full-width bordered row with the words "Use
   branches" beside the box; the words are now a label above it, so what is
   left is the box alone.

   inline-flex so it hugs the checkbox instead of stretching across the
   340px column - a 340px bordered box with a 17px tick in the middle of it
   looks like a field that failed to load. It keeps the input's border,
   radius and background so it rhymes with the three controls above it.

   align-self: flex-start is NOT decoration. .setting-control is a column
   flex box, and a flex item is blockified - inline-flex is ignored and the
   default align-items: stretch spreads this across the whole column, which
   is exactly the 340px box the previous paragraph exists to avoid. Same
   trap as .guest-badge inside .stay-who; the fix is the same line.

   It stays a <label> so the whole box is clickable. That is its only job
   beyond decoration, and a <span> here would leave the bare 17px checkbox
   as the only target on the row. */
.setting-checkbox {
  display: inline-flex; align-items: center; justify-content: center;
  align-self: flex-start;
  margin: 0; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: 7px; background: #FCFBF8;
  cursor: pointer;
}
.setting-checkbox input { flex-shrink: 0; width: 17px; height: 17px; margin: 0; }

/* A subordinate setting whose parents are not all switched on (update 260;
   the first of them arrived at 256 with no styling at all). The browser
   greys the box itself; this fades the LABEL and the note with it, so the
   whole row reads as unavailable rather than just the 17px control.

   :has() IS DOING REAL WORK HERE AND IS NOT A FLOURISH. app.js enables and
   disables these boxes LIVE as their parents are ticked, without a reload
   - so the fade has to follow the `disabled` property rather than a class
   the server rendered. A class would need app.js to toggle the row as well
   as the input, which is a second thing to keep in step and the exact kind
   of pair that drifts. :has() cannot drift: it is reading the same
   attribute that makes the control unusable.

   The NOTE beside it is deliberately NOT faded. It is the only thing on
   the row that says WHY the control is unavailable, and fading the
   explanation along with the thing it explains is how a greyed setting
   becomes a mystery. Same rule .section-off-note follows on this page. */
.setting-row:has(.setting-checkbox input:disabled) .setting-control { opacity: 0.55; }

/* Colour and size come from the .hint selector near the top of this file -
   see the note there. These are the layout half only. */
.setting-note p { margin: 0 0 9px; }
.setting-note p:last-child { margin-bottom: 0; }

/* Outside the grid, so it sits under the left column rather than becoming
   a row of its own. Same top border as the rows, which makes it read as
   the foot of the form rather than a fifth setting. */
.setting-actions {
  padding-top: 18px;
  border-top: 1px solid var(--line);
}

@media (max-width: 1000px) {
  /* Back to one column - see the arithmetic above for why here and not at
     820px like most of this file.

     Source order is setting then note, so it stacks into exactly the
     layout this page had before 239, bar the roomier label. The phone view
     is otherwise unchanged.

     THE max-width IS DOING A JOB THE MARKUP USED TO DO. This form carried
     style="max-width:460px" until 239, which is what kept its inputs from
     stretching the width of the card. Columns needed that gone, so the cap
     lives here instead, on the rows rather than the form - and it has to
     be on .setting-actions too, or the button's rule runs wider than the
     rows above it and the foot of the form stops lining up. 520 rather
     than 460 because the note now shares the column and wants the room; it
     is still well inside a readable measure. */
  .setting-row, .setting-actions { max-width: 520px; }
  .setting-row {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 10px;
    padding: 14px 0;
  }
  .setting-row:first-of-type { padding-top: 2px; }
  .setting-actions { padding-top: 14px; }
}

/* ---- Tier locks and the plan cards (update 309) ---------------------- */

/* THE SENTENCE BESIDE A SWITCH THE PLAN DOES NOT INCLUDE, and the same one
   above the greyed Add person form on Manage house. One class in both
   places because it is one idea: this control exists, it is off, and the
   reason is the plan rather than anything the reader did.
   ~
   IT IS DELIBERATELY NOT FADED. The rule further up this file fades
   .setting-control when its checkbox is disabled and stops short of
   .setting-note, for exactly this reason - fading the explanation along
   with the thing it explains is how a greyed setting becomes a mystery.
   This lives inside .setting-note, so it inherits that. */
.tier-locked {
  margin: 0 0 9px;
  font-size: 13px;
  color: var(--ink-soft);
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px;
}
.tier-locked strong { color: var(--ink); font-weight: 600; }

/* A BUTTON NOW (update 309i), where 309 made it an underlined link inside
   the sentence. The sentence explains why a control is dead; the reader
   wants the way out, and a footnote is not what a way out looks like.
   ~
   IT IS STILL AN ANCHOR, and on Settings it has to be: that page is one
   large form, so a real <button> would save the house settings on click.
   .btn already covers an <a> - it sets display and drops the underline -
   so this only has to shrink it. It sits beside 13px text and the full
   .btn size would tower over the line.
   ~
   align-self: flex-start BECAUSE .tier-locked IS A BASELINE-ALIGNED FLEX
   ROW. Without it the button stretches to the height of a wrapped
   sentence beside it. */
.tier-upgrade {
  padding: 5px 11px; font-size: 12px;
  white-space: nowrap; flex: none; align-self: flex-start;
}

/* WRAPS RATHER THAN RESTACKING AT A BREAKPOINT, the same way .house-cards
   does. A narrow screen gets one card per line for free and there is no
   media query to keep in step with the card width. */
/* THE BOTTOM MARGIN IS LARGER THAN THE TOP ON PURPOSE (update 309g). The
   cards are a block of their own and the Houses heading below them is a
   new subject; at 6px the two read as one run of content and the eye had
   nothing to stop at. The gap above stays small because the heading
   directly over it belongs to these cards. */
/* THE BOTTOM MARGIN CAME BACK DOWN AT 311i. 309g opened it to 34px to
   separate the cards from the Houses heading that used to follow them in
   the same card; that heading is a tab of its own now, so the cards are
   the last thing in theirs and the card's own padding does the work. */
.tier-cards {
  display: flex; flex-wrap: wrap; gap: 14px; margin: 14px 0 0;
}
.tier-card {
  flex: 1 1 200px; min-width: 200px;
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 16px 18px; background: var(--card);
}
/* THE SAME TREATMENT .house-card.current GETS ON THE PICKER, and the same
   variables, because it is the same idea: one card in a row means "this is
   where you are". Reusing --today-bg and --pine rather than inventing a
   tint keeps the two rows recognisably the same language - and it is the
   only card here allowed a colour, for the reason that rule gives. */
.tier-card.current {
  background: var(--today-bg); border-color: var(--pine);
  box-shadow: inset 0 0 0 1px var(--pine);
}
/* THE NAME AND THE BADGE ON ONE LINE (update 309d). baseline, so the badge
   sits on the tier name's baseline rather than centred against a Fraunces
   cap height - the same reasoning .house-pick-head carries. */
/* THE HEAD IS THE SAME HEIGHT ON EVERY CARD, WRAPPED OR NOT (update 311n).
   ~
   Only ONE card carries "(14 days left!)" - it comes from
   accounts.expires_at, so it can only ever appear on the plan the reader
   is on. Since 311i put the section menu beside these cards the column is
   narrower, and on that one card the aside wraps to a second line. Its
   Limits and Features then sat a line lower than the two beside it, and
   the row stopped scanning across.
   ~
   min-height IS THE FIX AND IT COSTS WHITESPACE ON THE OTHER TWO. That is
   unavoidable: any way of lining up what follows a variable-height block -
   subgrid included - reserves the tallest height on every card. Better a
   little air under two tier names than three misaligned columns.
   ~
   THE ARITHMETIC, AND RECOMPUTE IT IF THE TYPE CHANGES. The h3 is 17px at
   the inherited line-height of 1.5, so each of its line boxes is 25.5px -
   including the wrapped one, because the h3's own strut sets the minimum
   however small the text inside it is. Two lines is 51px.
   ~
   THE SPACE FALLS BELOW THE CONTENT. A single flex line in a taller
   container sits at the start, so the name and the badge stay put and the
   slack appears underneath, where it reads as breathing room. */
.tier-card-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 8px; margin-bottom: 12px; min-height: 51px;
}
.tier-card h3 {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 17px; margin: 0;
}

/* h4 IS NOT IN THE BASE HEADING RULE at the top of this file, which covers
   h1 to h3 only - so without this these two would arrive as the browser's
   default bold Times with margins big enough to double the card's height.
   Scoped to the card rather than added to the global rule, because nothing
   else in Roomee uses an h4 and a global one would be a decision made on
   behalf of pages that do not exist yet. */
.tier-card h4 {
  font-family: 'Fraunces', Georgia, serif; color: var(--pine-dark);
  font-weight: 600; font-size: 13px;
  text-transform: uppercase; letter-spacing: 0.04em;
  margin: 14px 0 5px;
}

/* SHARED SIZE ONLY. The two badges are different kinds of thing and only
   their dimensions are the same - Upgrade is an action, "Your plan" is a
   statement.
   ~
   THE CURSOR MOVED DOWN AT 311, exactly as the note here said it would.
   The Upgrade badge is a real submit button now and wants .btn's pointer;
   only "Your plan" stays inert, and it keeps `cursor: default` on its own
   rule below. */
.tier-card-badge {
  padding: 5px 11px; font-size: 12px;
  white-space: nowrap; flex: none; border-radius: 7px; font-weight: 600;
}
/* The form exists only to carry one hidden field and one button, so it
   must add no box of its own to the card's heading row. */
.tier-card-form { display: contents; }

/* NOT A BUTTON, AND DELIBERATELY DOES NOT LOOK LIKE ONE (update 309e).
   It keeps the pill so the row lines up with the cards beside it and
   loses the fill, because a filled button promises an action and this one
   can never have a href - you are already on this plan.
   ~
   NO .btn CLASS AT ALL, so there is nothing to override and nothing to
   remember when .btn changes. It is a <span>, not an anchor, for the same
   reason: it is a label. */
.tier-card-badge-current {
  display: inline-block; cursor: default;
  /* BRASS AND WHITE, NOT --line ON TRANSPARENT (update 309f). The first
     version borrowed the hairline used to separate table rows, which is
     doing the opposite job - it is meant to be barely there - and on the
     current card's green tint it all but disappeared. Brass is the
     palette's second accent and the only one not already spoken for here:
     green means an action, red means destruction. */
  border: 1px solid var(--brass); color: var(--pine-dark);
  background: #fff;
}

/* "(14 days left)" beside the tier name. Inside the h3, so it has to take
   the body font and the ordinary weight back off it - otherwise a
   parenthetical arrives in 17px Fraunces semibold and reads as part of the
   plan's name. */
/* USED IN TWO PLACES SINCE 312b - beside the tier name on the plan card,
   and inside the sentence on Your billing. DO NOT SCOPE IT TO
   .tier-card: the point of sharing it is that the trial counts down in
   the same words, the same size and the same red wherever it appears, and
   a reader who sees "2 days left!" twice on one page must see the same
   thing twice. The name is now a little wrong and the behaviour is right;
   renaming it is a bigger edit than it is worth. */
.tier-card-days {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 13px; font-weight: 400;
  color: var(--ink-soft); white-space: nowrap;
}
/* THE LAST FEW DAYS (update 309m, brightened at 309n).
   ~
   THE SAME LITERAL THE FEATURE CROSSES TAKE, not --danger. 309m used the
   palette red on the reasoning that warning TEXT belongs with Roomee's
   other warning text - which is true of the red on a Delete button or a
   conflict banner, where the job is to slow somebody down at the moment
   they act. THIS ONE HAS THE OPPOSITE JOB. Nobody is doing anything when
   they read it; it has to interrupt a page they were scanning past and
   turn into a reason to press the button beside it. A brick red that
   behaves itself is the wrong tool for that.
   ~
   SO THIS IS NOW THE THIRD PLACE #DE3B2B APPEARS, WHICH IS THE POINT AT
   WHICH IT EARNS A VARIABLE - see the note at 309j, which said exactly
   that. It is not promoted yet because these two uses are still one card;
   if a fourth arrives anywhere else, --alert-red goes in :root and all
   three change together.
   ~
   NO BACKGROUND, NO BORDER, NO BADGE. It sits inside the tier name's
   heading, and anything boxed there would compete with the plan's own
   badge on the same line. Bright red and bold is the whole treatment. */
.tier-card-days.urgent { color: #DE3B2B; font-weight: 600; }

/* NO BORDER AND NO ZEBRA. .plain gives this table the shared cell padding
   and type, but a two-row list of limits inside a card is not a data grid
   and a rule under each row would cut the card in half twice. */
/* THE LIMITS TABLE IN A NARROW CARD (tightened at 311o).
   ~
   table.plain SETS overflow-wrap: anywhere on every cell, which exists to
   stop one long NAME setting a table's width. In a 200px card it was
   breaking "Unlimited" into "Unlimite / d" and, in doing so, stealing
   enough width from the left column to push "house" off the end of "Max
   people per house". Two faults from one rule, and the same one
   .nights-table.is-wide already overrides for the same reason.
   ~
   overflow-wrap AND white-space ANSWER DIFFERENT QUESTIONS: the first is
   whether a WORD may be broken, the second whether a LINE may wrap between
   words. The value needs both - "Unlimited" must not split and must not
   drop below its own label.
   ~
   width: 1% ON THE VALUE COLUMN IS THE OLD TABLE TRICK for "shrink to
   content". A percentage smaller than the content can be is treated as a
   minimum, so the column takes exactly the width of its longest value and
   the label column gets everything left over. Without it the two columns
   negotiate, and the negotiation is what produced the mid-word break.
   ~
   THE LABEL IS STILL FREE TO WRAP, and it is the right thing to give way:
   it breaks at a space and stays readable, where a number cannot. Since
   311p dropped "Max" from both labels it should not need to at any width -
   but the rule stays, because a longer label on a future tier row would
   otherwise start breaking words again. */
.tier-card-limits { width: 100%; }
/* A gap between the two columns, so a wrapped label never runs into the
   number beside it. On the first cell only, since the second is
   right-aligned against the card's own padding. */
.tier-card-limits td { padding: 3px 0; font-size: 13px; border: 0; }
.tier-card-limits td:first-child { color: var(--ink-soft); padding-right: 10px; }
.tier-card-limits td:last-child {
  text-align: right; font-weight: 600; color: var(--ink);
  width: 1%; white-space: nowrap; overflow-wrap: normal;
}

.tier-card-features { list-style: none; margin: 0; padding: 0; }
.tier-card-features li {
  font-size: 13px; color: var(--ink-soft); margin-bottom: 4px;
  display: flex; align-items: center; gap: 7px;
}
/* THE ICON TAKES currentColor, so these two lines colour the whole row -
   which is why the icons need no colour of their own. The icon is
   aria-hidden and the meaning is carried by the .sr-only word beside it,
   so this is decoration and nothing depends on it being seen. */
.tier-card-features li.yes { color: var(--ink); }
/* BRIGHTER THAN THE PALETTE, AND THE ONLY TWO COLOURS IN ROOMEE THAT ARE
   (update 309j). --pine is a deep, desaturated green chosen to sit behind
   white text on a button, and --danger is a brick red chosen not to shout
   on a page somebody is about to delete something from. At 15px, as a
   1.4px stroke, both go muddy - the tick reads charcoal and the cross
   reads brown, and the column stops answering "yes or no" at a glance.
   ~
   THESE ARE ICONS ON A WHITE CARD, WHICH IS A DIFFERENT JOB. Nothing sits
   on top of them and they carry no text, so they can be as saturated as
   they need to be to read at a glance from a metre away, which is how a
   pricing table is actually looked at.
   ~
   HARD-CODED, NOT ADDED TO :root. Two literals used in two rules on one
   card are not a palette; promoting them would invite their use somewhere
   they would clash with --pine sitting next to them. If a third place
   ever wants them, that is the moment they earn a variable.
   ~
   NOTHING DEPENDS ON SEEING THEM. Both icons are aria-hidden and the
   meaning is carried by the .sr-only word beside each, so this is
   decoration - which is also why it is safe to depart from the palette
   here and nowhere else. */
.tier-card-features li.yes .tier-icon { color: #1E9E52; }
.tier-card-features li.no .tier-icon { color: #DE3B2B; }
.tier-icon { width: 15px; height: 15px; flex: none; }

/* THE HOLDER LINE ON THE HEADING ROW (update 309d), same shape as
   .house-pick-head: baseline aligned, wrapping onto its own line on a
   narrow screen rather than squeezing the heading, and the h1 keeps its
   own bottom margin so nothing below it moves. */
.account-head {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: 4px 14px;
}
.account-head .hint { margin: 0 0 0.4em; }

/* The fieldset around the Add person form. It exists ONLY to carry a
   single disabled attribute over every control inside it, so it must
   contribute no box of its own - a fieldset's default border, margin and
   padding would put a visible frame round half the People card. */
.person-add-fields { border: 0; margin: 0; padding: 0; min-width: 0; }

/* THE ADD PERSON FORM AT THE PEOPLE CAP (update 310i). The fourth of these
   after .branch-editors, .tshare-editors and .rate-editors, and it takes
   the same two values so the four look alike when they are off - 0.55 and
   the same flat fill on the inner row.
   ~
   THE FADE AND THE DISABLING ARE SEPARATE JOBS. .person-add-fields carries
   the disabled attribute, which stops every control inside working; this
   only says so. Until 310i the form was unusable and looked untouched, so
   the only sign anything had changed was that clicking did nothing.
   ~
   ONE DECLARATION, NOT TWO. The other three also flatten their inner row's
   background - .branch-row, .rate-period and .tshare-block are CARDS with
   a fill of their own, and a tinted card at 0.55 reads as a card rather
   than as something switched off. .person-add-row is not a card: it is a
   bare grid of fields with no background at all, as its own comment says.
   Copying the second line across would have PAINTED a grey panel behind
   fields that never had one, which is a change of shape rather than a
   fade.
   ~
   THE NOTE ABOVE IT IS NOT INSIDE THIS, and must not be. It is the only
   thing explaining why the form is faded, and 272 records what happened
   the last time such a note ended up inside its own faded block. */
.people-add.is-off { opacity: 0.55; }


/* ---- Tier card cost and tooltips (update 309f) ----------------------- */

/* THE ONE BIG NUMBER ON THE CARD, which is what makes it scannable down a
   row of three. Fraunces because it is a headline figure rather than data,
   and the same face the tier name uses. */
.tier-card-price {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 26px; font-weight: 600; color: var(--pine-dark);
  margin: 0; line-height: 1.15;
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px;
}
.tier-card-per {
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 12px; font-weight: 400; color: var(--ink-soft);
}

/* THE HELP BUTTON. Pushed to the end of the row by margin-left:auto so the
   icons line up down the right edge of the card whatever the labels do -
   "Timeshare" and "House closures" are very different widths. */
.tier-feature-label { flex: 1 1 auto; }
.tier-help {
  flex: none; margin-left: auto;
  background: none; border: 0; padding: 0; cursor: help;
  color: #C0B9A6; line-height: 0;
  position: relative;
}
.tier-help:hover, .tier-help:focus-visible { color: var(--pine); }
.tier-help-icon { width: 14px; height: 14px; display: block; }

/* THE BUBBLE. Built from data-tip on ::after rather than from the browser's
   own title attribute, which cannot be styled, takes a second to appear
   and never appears at all on a touch screen.
   ~
   ON :focus AS WELL AS :hover, AND THAT IS THE POINT. The button takes
   focus when it is tapped, so a phone gets the explanation from the same
   rule a mouse does. Dropping the focus half would make this desktop-only
   without anything looking broken.
   ~
   ::before IS THE ARROW. Both are on the button, which is position:
   relative, so the bubble travels with the icon and needs no measuring.
   ~
   white-space: normal AND A WIDTH, because a sentence in a nowrap tooltip
   runs off the side of the screen. 200px is narrower than the card so it
   cannot push the layout out.
   ~
   NOT CLIPPED BY THE CARD. .tier-card sets no overflow, deliberately - a
   bubble that pops above the top row of features would be cut in half by
   overflow: hidden, and nothing here needs it. */
.tier-help::after {
  content: attr(data-tip);
  position: absolute; bottom: calc(100% + 8px); right: -6px;
  width: 200px; padding: 8px 10px;
  background: var(--pine-dark); color: #fff;
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 12px; font-weight: 400; line-height: 1.4;
  text-align: left; white-space: normal; border-radius: 7px;
  box-shadow: 0 4px 14px rgba(38, 46, 39, 0.22);
  opacity: 0; visibility: hidden; transition: opacity 0.12s ease;
  z-index: 5;
}
.tier-help::before {
  content: ''; position: absolute; bottom: calc(100% + 3px); right: 0;
  border: 5px solid transparent; border-top-color: var(--pine-dark);
  opacity: 0; visibility: hidden; transition: opacity 0.12s ease;
  z-index: 5;
}
.tier-help:hover::after, .tier-help:focus::after,
.tier-help:hover::before, .tier-help:focus::before {
  opacity: 1; visibility: visible;
}

/* ---- Switched-off people (update 310) -------------------------------- */

/* A BLOCK, NOT A CARD. It sits inside the People card under the live rows,
   so it needs a rule above it to separate the two and nothing else - a
   nested card would read as a second subject on a page that has one. */
.person-off-block {
  margin-top: 26px; padding-top: 20px; border-top: 1px solid var(--line);
}
.person-off-block h3 { font-size: 16px; margin: 0 0 4px; }
.person-off-block .hint { margin: 0 0 14px; }

/* baseline, so a name and a button sit on one line, and wrap so a long
   name plus the plan-full sentence drops rather than squeezing. */
.person-off-row {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: 8px 14px;
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
.person-off-row:last-child { border-bottom: 0; }
/* --ink-soft, NOT --ink. They really are switched off, and a name in full
   strength next to the live rows above would read as another live one. */
.person-off-name { font-weight: 600; color: var(--ink-soft); }
.person-off-note {
  font-size: 13px; color: var(--ink-soft);
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px;
}


/* ---- Changing plan (update 311) -------------------------------------- */

/* THE CHOICE LISTS on the houses and people stages. A stack of rows rather
   than a grid: the only thing on each line is a checkbox and a name, and
   two columns of that would put a hand's width of nothing between them. */
.tier-choice { margin: 0 0 18px; }
.tier-choice-row {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
.tier-choice-row:last-child { border-bottom: 0; }
.tier-choice-row input { width: 16px; height: 16px; flex: none; margin: 0; }
.tier-choice-row em { color: var(--ink-soft); font-style: normal; font-size: 12px; }
/* The admin's row: ticked, disabled, and not offered. Faded so it reads as
   a statement rather than a control somebody failed to reach. */
.tier-choice-row.is-fixed { opacity: 0.6; }
/* A row that cannot be ticked because the limit is reached (update 311e).
   Lighter than .is-fixed: the admin's row is a permanent statement, this
   one comes back the moment something else is unticked. */
.tier-choice-row.is-full { opacity: 0.45; }

/* The button pair at the foot of every stage. The back-out sits beside the
   action rather than under it - it is an equal choice at every stage,
   because nothing has been written yet. */
.tier-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 4px; }

/* The confirm screen's three sections. h3 subdivides a section by house
   and only appears where a section has to name more than one thing, so it
   sits tighter to its list than the h2 above it does. */
.tier-confirm h3 { font-size: 14px; margin: 12px 0 4px; }
.tier-list { margin: 0 0 14px; padding-left: 20px; }
.tier-list li { margin-bottom: 5px; font-size: 14px; }
/* THE LINES THAT CANNOT BE UNDONE. The one place on the confirm screen
   where red is used, and it is the palette's --danger rather than the
   brighter alert red: this is text meant to slow somebody down at the
   moment they act, which is exactly what --danger is for. */
.tier-permanent { color: var(--danger); font-weight: 600; }

/* The permanent items inside the closing paragraph. A block so the
   sentences sit on their own lines under the bold lead rather than running
   into it, and --danger because this is text meant to slow somebody down
   at the moment they act - the same job the Delete buttons' red does, and
   not the brighter alert red, which is for catching an eye that is
   scanning past. */
.tier-permanent-list {
  display: block; margin: 6px 0; color: var(--danger); font-weight: 600;
}

/* A CARD'S HEADING WITH SOMETHING AT THE RIGHT-HAND END (update 311k).
   ~
   THE THIRD COPY OF THIS SHAPE, after .house-pick-head and .account-head,
   which is the point at which it stops being a page's own layout and
   becomes a pattern - so this one is named for what it is rather than for
   the page it appears on, and the next card that wants a button beside its
   heading takes it as it stands.
   ~
   baseline, NOT center: the button's text should sit on the heading's
   baseline rather than being centred against a Fraunces cap height, which
   reads slightly high at every size.
   ~
   NO MARGIN OF ITS OWN. The h2 keeps the 0.4em it has everywhere else, so
   the gap under a heading is the same whether or not it has a button
   beside it. A flex item's bottom margin counts towards the line height,
   so the button still lands on the baseline. */
.card-head {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: 8px 14px;
}

/* THE TWO BUTTONS IN THE HEADER BAR (update 311m). One class for both, and
   that is the whole fix.
   ~
   AN ANCHOR AND A BUTTON WITH THE SAME PADDING ARE NOT THE SAME HEIGHT.
   The anchor inherits the page's line-height of 1.5 - so 12px text
   reserves an 18px line box - while a <button> takes the browser's own
   `normal`, around 1.15, and reserves about 14px. Four pixels, entirely
   invisible in the markup, which is why 311l set identical inline padding
   on both and still produced two different heights.
   ~
   line-height: normal PUTS THEM ON THE SAME FOOTING rather than picking a
   number: it is what the button was already using, so Sign out does not
   move and the anchor comes to meet it.
   ~
   font-family: inherit UNDOES THE UA STYLESHEET, which gives a <button>
   Arial regardless of what the page is set in. .btn never said it, and
   .house-card carries the same line with the same note for the same
   reason.
   ~
   SHARED RATHER THAN INLINE ON EACH. Two adjacent controls that must match
   are one decision, and the pair of inline styles they had were two places
   to keep in step - which is what let them drift apart in the first
   place. */
.nav-btn {
  padding: 5px 12px; font-size: 12px;
  line-height: normal; font-family: inherit;
}

/* THE BILLING LINE AND ITS BUTTON (update 312b). The sentence takes the
   slack and the button sits at the right-hand end; on a narrow screen the
   button drops under it rather than squeezing the text into a column.
   ~
   Same shape as .people-cap-note, and for the same reason - a sentence
   explaining a state, with the one thing that changes it beside it. */
.billing-line {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 8px 14px;
}
.billing-line > span { flex: 1 1 260px; }


/* ---- Calendar sync on My profile (update 313) ---- */
/* The generated URL and its Copy button. .cal-link-box gets the same
   #FCFBF8 fill every other "here is a thing to act on" panel in Roomee
   uses - .family-list, .stay-row - so it reads as one object rather than
   as a stray field on the card. */
.cal-link-box {
  border: 1px solid var(--line); border-radius: 8px;
  padding: 14px 16px; margin-bottom: 18px; background: #FCFBF8;
}
.cal-link-box label {
  display: block; margin-bottom: 6px;
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--ink-soft);
}
.cal-link-row { display: flex; gap: 8px; align-items: stretch; }
/* min-width: 0 ON THE FIELD, NOT ONLY ON THE ROW. A flex item's default
   min-width is auto, which for an input resolves to its `size` - about 20
   characters - so a 90-character URL pushes the Copy button off the side
   of a phone. This is the same trap minmax(0, 1fr) solves on the page
   grids, arriving through flexbox instead. */
.cal-url {
  flex: 1; min-width: 0;
  font-family: 'IBM Plex Mono', monospace; font-size: 12.5px;
}
.cal-link-row .btn { flex: 0 0 auto; }

/* The instructions, shut by default.
   ~
   A PLAIN <summary> IN THE CARD'S OWN TYPE, and 313c's version is worth a
   note because it looked like the right answer. That one borrowed the
   collapsed-month convention - .cal-monthhead / .cal-nothing, mono
   uppercase heading, a quieter second line, a + turning into a minus -
   which Roomee has used for a shut-thing-you-can-open since 230, on the
   calendar's empty months and on Timeshare's past blocks.
   ~
   BOTH OF THOSE ARE STRUCTURAL DIVIDERS IN A LONG STACK OF LIKE THINGS,
   where a mono uppercase rule is one of several marking the stack out.
   This is one collapsible paragraph in the middle of a card of ordinary
   prose, and the same treatment made it the only line on the section in a
   different typeface. Update 255's rule applies and points the other way
   from the obvious reading: when a borrowed control looks wrong in its new
   home, adjust the borrowing - and here the adjustment was not to borrow.
   ~
   The native marker is kept, which is why there is no list-style: none
   here and no ::after carrying a +. Both existing instances remove the
   triangle because they supply their own marker; this one has nothing to
   supply. */
.cal-how { margin-bottom: 18px; }
.cal-how > summary {
  cursor: pointer; font-size: 13.5px; font-weight: 600; color: var(--pine-dark);
  padding: 4px 0;
}
.cal-how-body { padding: 10px 0 0; }
.cal-how-body p { margin: 0 0 10px; font-size: 13.5px; line-height: 1.55; }
.cal-how-body p:last-child { margin-bottom: 0; }

.cal-state { margin: 0 0 14px; font-size: 14px; }
/* "Last checked" - the one line that answers whether any of this works.
   Muted, because it is a status rather than an instruction, but not
   .hint-sized: somebody reading this card is reading it FOR this line. */
.cal-checked {
  margin: 0 0 18px; padding: 10px 12px;
  border-radius: 7px; background: #F7F5F0;
  font-size: 13.5px; color: var(--ink);
}

.cal-prefs { margin-bottom: 20px; }
/* A fieldset with the browser's own border and padding removed. The legend
   is kept and styled as a field label, so the group is announced properly
   to a screen reader while looking like every other label on the page. */
.cal-scope { border: 0; padding: 0; margin: 0 0 14px; }
.cal-scope legend {
  padding: 0; margin-bottom: 8px;
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--ink-soft);
}
.cal-radio, .cal-check {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 9px 2px; margin: 0; cursor: pointer;
  font-size: 14px; font-weight: 400; color: var(--ink);
}
/* margin-top rather than align-items: center. The label beside these runs
   to two or three lines on a phone, and a centred control drifts to the
   middle of the block where it stops looking attached to the first line. */
.cal-radio input, .cal-check input { flex-shrink: 0; margin-top: 3px; }
/* The closures row, when it renders at all. margin-bottom rather than a
   margin on the Save button, deliberately: the row is CONDITIONAL - it is
   absent whenever the house has closures switched off - and hanging the
   gap on the button would space the form differently in the two states.
   The fieldset above already carries the same 14px, so the button sits the
   same distance below whichever control ends up last. */
.cal-check {
  padding-top: 12px; margin-bottom: 14px;
  border-top: 1px solid var(--line);
}
.cal-radio-note {
  display: block; margin-top: 2px;
  font-size: 12.5px; line-height: 1.5; color: var(--ink-soft);
}

/* The two destructive buttons, fenced off below a rule. Side by side on a
   wide screen and wrapping on a narrow one - flex-wrap rather than a media
   query, since there are only two of them and no alignment to preserve. */
.cal-danger {
  display: flex; flex-wrap: wrap; gap: 10px;
  padding-top: 16px; border-top: 1px solid var(--line);
}
.cal-danger form { margin: 0; }

@media (max-width: 600px) {
  /* Stacked, because two buttons of different widths side by side on a
     phone leaves the second one looking like an afterthought - and the
     more dangerous of the two is the second. */
  .cal-danger { flex-direction: column; align-items: stretch; }
  .cal-danger .btn { width: 100%; }
}

/* ---- Draft picking (update 316) ---- */
/* Appended rather than spliced in beside the timeshare rules it resembles.
   Block edits to the middle of this file have twice taken unrelated rules
   with them, which is the reason the confirmation dialog sits at the bottom
   too. */

/* THE FADE IS A SEPARATE SELECTOR FROM .tshare-editors, not a shared one,
   and that is the same call 258 made when .branch-editors was written
   beside .rate-editors. The two are about different settings with different
   promises, and one rule covering both would make "keep them identical" the
   default the day one of them should not be. */
.draft-editors.is-off { opacity: 0.55; }

/* minmax(0, 1fr) AND NOT A BARE 1fr. A bare 1fr is min-content-sized, so a
   date input - which has an intrinsic minimum wider than it looks - pushes
   the track and the grid overflows its card on a phone. Every mobile grid
   in this stylesheet carries the same fix. */
/* CAPPED AT 520px (316c), matching .draft-order-list below and the room
   preference list it copies. Four date inputs stretched across a 900px card
   is four controls that need about 150px each wearing 400 - and a date
   input is one of the few controls whose intrinsic width is genuinely the
   right one, so the extra was pure whitespace inside a border.

   THE CAP IS ON THE GRID, NOT ON THE FORM. .draft-form also holds the two
   checkboxes, the hint paragraphs and the buttons, and the paragraphs want
   the card's full measure - capping the form would set them to 520px as
   well and give this card a text column narrower than every other card on
   the page. Same reasoning that took max-width off the Settings form at
   239 and put the cap on .setting-row instead. */
.draft-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px 16px; margin-top: 4px; max-width: 520px;
}
.draft-field { display: flex; flex-direction: column; gap: 6px; }
.draft-field label { margin-bottom: 0; font-size: 13px; color: var(--ink-soft); }
.draft-field input { width: 100%; }
/* Rounds is a small number beside four dates, so it takes one track rather
   than stretching across both. */
.draft-field-rounds input { max-width: 120px; }

/* IT STAYS TWO COLUMNS ON A PHONE (316g), where every other grid in this
   stylesheet collapses to one.

   THE FOUR DATES ARE TWO PAIRS AND THE PAIRING IS THE INFORMATION. Opens
   beside closes, first night beside last night: stacked in a single column
   they become four unrelated fields, and the reader has to hold "these two
   are a range" in their head instead of seeing it. That is worth more here
   than the extra width each control would get, which is the trade every
   other grid makes the other way because its fields are independent.

   THE GAP TIGHTENS INSTEAD. 10px between columns rather than 16 gives each
   date input most of what a single column would have, and a date input is
   one of the few controls whose intrinsic width is genuinely the right one -
   there is nothing here that wants to be wide.

   minmax(0, 1fr) IS DOING REAL WORK AT THIS SIZE and is not the usual
   defensive copy. A date input has an intrinsic minimum wider than it
   looks, so a bare 1fr pushes the track and overflows the card - which is
   invisible at desktop width and is exactly what this rule now exposes. On
   a very narrow phone the browser may truncate the displayed date rather
   than the box: that is the cost, it was weighed, and the pairing is worth
   it.

   Draft rounds and the month-lock checkbox share the third row and are
   unaffected either way - neither wants width. */
@media (max-width: 620px) {
  .draft-grid { gap: 12px 10px; }
}

/* THE TWO CHECKBOXES USE THE SETTINGS SHAPE (316a): a label of its own,
   then a second label wrapping the input so the whole bordered box is
   clickable. They were label-beside-box at 316, which is a different
   control, and two shapes for one thing on two cards of the same page is
   how a reader stops trusting either.

   A COLUMN FLEX BOX WITH A GAP, exactly as .setting-control is, so the
   space between a label and its box belongs to the container. A margin rule
   is the obvious way and is wrong: the box IS a <label> too, so
   `.draft-toggle label { margin }` lands on both and out-specifies
   .setting-checkbox (0,1,1 against 0,1,0) into the bargain. A gap cannot
   pick the wrong child. Same reasoning written out at .setting-row.

   align-items: flex-start, WHICH IS THIS TRAP'S FIFTH OUTING after
   .setting-checkbox, .guest-badge, .stay-name and the closure row. A flex
   item is blockified, so the inline-flex on .setting-checkbox is ignored
   and the default `stretch` spreads a bordered box holding a 17px tick
   across the full width - which reads as a field that failed to load. On
   the container here rather than align-self on the child, because both
   children want it. */
.draft-toggle {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 9px; margin-top: 18px;
}
.draft-toggle > label { margin-bottom: 0; }

/* A .draft-toggle INSIDE THE GRID IS A GRID CELL (316f), so it loses the
   top margin that separates it from whatever it follows in normal flow -
   the grid's own row gap does that job, and both together would push its
   row out of line with the fields beside it.

   BY DESCENDANT RATHER THAN A MODIFIER CLASS, so a toggle OUTSIDE the grid
   keeps its spacing without anybody having to remember which of two classes
   to reach for. The randomise box below the grid is exactly that case.

   The label also takes .draft-field's own size and colour, or the two cells
   sharing a row would have labels a point apart and in different inks -
   which reads as one of them being a heading. */
.draft-grid .draft-toggle { margin-top: 0; }
.draft-grid .draft-toggle > label { font-size: 13px; color: var(--ink-soft); }

.draft-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 16px; }

/* THE TWO BANNERS ARE .section-off-note NOW, AND THIS IS ALL THAT IS LEFT
   OF THEM (316c).

   At 316 they were .draft-state and .draft-turn, each with a border, a fill
   and a padding of its own - a fourth and fifth kind of box on a page that
   already had three, saying nothing the existing ones could not. The card
   already uses .section-off-note three times (the switched-off note, the
   frozen-settings note, the branch warnings) and .people-cap-note already
   IS the sentence-plus-button shape, written at 310h with a comment asking
   to be reused rather than copied. So both banners now use those, and this
   is the only rule that remains.

   .draft-live BORROWS .notice-banner's OWN PAIR rather than a new one, and
   that is the point rather than laziness. Green-on-pale already means "this
   is live and it is fine" everywhere in Roomee, so a reader who has seen a
   save confirmation knows what an open draft is without being taught. The
   two are written out here rather than @extend-ed because CSS has no
   @extend; if .notice-banner's colours are ever changed, change these with
   them - they are one decision in two places and neither is the master.

   COLOUR IS THE SECOND SIGNAL, NEVER THE ONLY ONE. The sentence says which
   state it is in words - "Picking is open", "Not open yet", "Finished" -
   the same way the past/upcoming split on the block list is labelled rather
   than left to the muting. */
.draft-live {
  border-color: var(--tag-2); background: #EDF3EE; color: var(--pine-dark);
}

/* THE ROWS ARE .pref-item AND INHERIT EVERYTHING FROM IT - the handle, the
   rank, the arrows and the row chrome, in that order, which at 316a is the
   same order the room preference rows use. It was rank, name, arrows at
   316: no handle, and the arrows pushed to the far right by a name that
   grew. Nothing about that was deliberate.

   THE NAME DOES NOT GROW. It sits after the arrows, exactly where
   .pref-room does, and takes its own width - the ROW's width comes from the
   cap below, not from the name stretching to fill it. A `flex: 1 1 auto`
   here is what pushed the arrows away, so it is gone rather than moved. */
.draft-item-name { color: var(--ink); }

/* CAPPED, MATCHING THE ROOM PREFERENCE LIST (316a), which does the same
   thing inline. Left off at 316, so the rows ran the full width of the card
   and a three-word branch name sat alone on a 900px line. A class rather
   than an inline style because there are two of these lists and they must
   not drift apart. */
.draft-order-list { max-width: 520px; }

/* A LIST GREYED BY data-disables. It is NOT the same as .draft-editors.is-off
   above even though both fade: this one also has to lose the grab cursor,
   because a row that still looks draggable under a hand that cannot drag it
   is the thing the JavaScript half of data-disables exists to prevent, and
   the cursor is the part CSS owns. */
.draft-order-list.is-off { opacity: 0.55; }
.draft-order-list.is-off .pref-item { cursor: default; }

.draft-picks { list-style: none; margin: 8px 0 0; padding: 0; }
.draft-pick {
  display: flex; align-items: baseline; gap: 10px;
  padding: 8px 0; border-bottom: 1px solid var(--line); font-size: 14px;
}
.draft-pick:last-child { border-bottom: none; }
.draft-pick-round {
  font-family: 'IBM Plex Mono', monospace; color: var(--brass-deep);
  font-weight: 600; flex-shrink: 0;
}
.draft-pick-name { flex: 1 1 auto; }
.draft-pick-dates { color: var(--ink-soft); }
/* A SKIPPED TURN IS MUTED, NOT HIDDEN. It consumed a slot, so leaving it
   out would make the count in the state line disagree with the rows under
   it - and the record keeps the branch name on a skipped row precisely
   because somebody will ask about it in August. */
.draft-pick-skipped { color: var(--ink-soft); font-style: italic; }

/* THE FROZEN ORDER (316d) BORROWS .draft-previous rather than fading the
   drag list. A faded control that still looks draggable invites the drag;
   a list that is not a control does not. The modifier exists only to give
   it the same width as the list it replaces, so the section does not change
   shape the moment the first branch picks. */
.draft-order-frozen { max-width: 520px; }

/* THE ORDER BEING SET AND THE ORDER THAT RAN, SIDE BY SIDE (317g).
   Deciding who goes first is a comparison, and a comparison whose two
   halves are not on screen together is one the admin does from memory.

   align-items: flex-start so the reference column sits level with the top
   of the drag list rather than stretching down beside it - it is shorter
   whenever a branch has been added since, and a bordered-looking column of
   empty space reads as something that failed to load.

   THE DRAG LIST KEEPS ITS 520px CAP and the reference takes what is left:
   flex: 0 1 auto on the list so it can shrink but not grow past the cap
   .draft-order-list already sets. A static list of names wants no width;
   the list being dragged is the one whose rows have to stay comfortable. */
.draft-order-row { display: flex; gap: 28px; align-items: flex-start; flex-wrap: wrap; }
.draft-order-row > .draft-order-list { flex: 0 1 520px; }
.draft-order-last { flex: 1 1 180px; min-width: 0; }
.draft-order-last-head {
  margin: 0 0 4px; font-family: 'IBM Plex Mono', monospace;
  font-size: 12px; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--brass-deep);
}
/* Below the point where two columns stop being two columns, the reference
   drops under the drag list rather than squeezing beside it. flex-wrap does
   the work; this only stops the reference stretching the full width once it
   is on its own row, which would make it look like a second editor. */
@media (max-width: 760px) {
  .draft-order-last { flex-basis: 100%; }
  .draft-order-last .draft-previous { max-width: 520px; }
}

/* One collapsed row per finished draft (317h; it was one row for one draft
   at 317g, .draft-previous-picks). It borrows .tshare-past whole - the
   summary, the Show/Hide pair, the +/- - so all this does is space the rows
   and stop the last one's contents running into whatever follows.

   .tshare-group-past CARRIES A TOP BORDER, which is what separates the rows
   from each other without a rule of their own. That is why every row uses
   the -past variant and none uses -lead: -lead is for a boundary with
   nothing above it, and here the heading is always above. */
.draft-previous-draft + .draft-previous-draft { margin-top: 2px; }
.draft-previous-draft .draft-picks { margin-top: 4px; }
.draft-previous-draft > *:last-child { margin-bottom: 14px; }

.draft-previous { list-style: none; margin: 8px 0 0; padding: 0; }
.draft-previous li {
  display: flex; align-items: baseline; gap: 10px;
  padding: 6px 0; color: var(--ink-soft); font-size: 14px;
}

/* ---- Max block length, on the Timeshare card (update 316) ---- */
/* It sits ABOVE Add a block, because it governs what that form accepts and
   a limit read after the thing it limits is a limit somebody meets as a
   refusal. */
.tshare-max { margin: 0 0 22px; padding-bottom: 18px; border-bottom: 1px solid var(--line); }
.tshare-max label { display: block; margin-bottom: 6px; }
.tshare-max-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.tshare-max-row input { width: 110px; }
.tshare-max-unit { color: var(--ink-soft); }

/* ---- The branch head's picking screen (update 317) ---- */
/* IT WEARS THE CONFIRM PANEL'S CLOTHES AND IS NOT ONE. There is no <dialog>
   here and there must not be: showModal() is JavaScript-only, and 314's
   note says requiring JavaScript "would not have been acceptable for
   anything carrying information" - which choosing a family's fortnight in
   August squarely is. So the panel is a page, and these rules are what make
   a page read as something waiting for an answer. */
.draft-pick-shell { display: flex; justify-content: center; padding: 24px 0 40px; }
.draft-pick-panel {
  width: 100%; max-width: 560px;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 26px;
  box-shadow: 0 18px 44px rgba(38, 46, 39, 0.10);
}
/* SOFTER THAN .confirm-panel's SHADOW, deliberately. That one sits over a
   dimmed page and has to lift off it; this one sits on the page and only
   has to be the thing on it. */
.draft-pick-panel h1 { margin: 0 0 10px; font-size: 22px; }
/* THE SECOND HEADING ON THE ARRIVAL SCREEN (317b). Smaller than the h1, so
   the announcement and the part of it that applies to the reader read as
   one narrowing pair rather than two separate things: the draft is open,
   and it is your turn.

   THE ROUND AND BRANCH SIT ABOVE BOTH, not below - see the eyebrow rule
   under this one, and the note in views.ts about why 317b's arrangement
   was reverted at 317e.

   IT IS AN h2 AND NOT A STYLED p, because it IS a heading - the h1 is the
   announcement and this is the part of it that applies to the reader. A
   screen reader should get both, in that order. The h1's own bottom margin
   is tightened by the pair rule below rather than changed, so the two other
   steps keep the spacing they had. */
.draft-pick-sub { margin: 0 0 8px; font-size: 16px; font-weight: 600; color: var(--ink); }
.draft-pick-panel h1:has(+ .draft-pick-sub) { margin-bottom: 4px; }

.draft-pick-eyebrow {
  margin: 0 0 6px; font-family: 'IBM Plex Mono', monospace;
  font-size: 12px; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--brass-deep);
}
/* .draft-pick-eyebrow-under WAS HERE AND IS GONE (317e). It gave the
   arrival screen's eyebrow a larger bottom margin for the one update it
   spent below the headings. The eyebrow is above the heading on all three
   steps again, so there is one position and one margin. */
.draft-pick-lead { margin: 0 0 18px; color: var(--ink-soft); font-size: 14px; line-height: 1.5; }

.draft-pick-cards { display: flex; flex-direction: column; gap: 12px; }
/* CARDS THAT ARE ANCHORS. Both navigate and neither changes data, so an
   anchor is the honest element - and it is also what keeps both working
   with no script. display:block rather than flex on the anchor itself so
   the two spans stack without the link's own box becoming a flex parent
   that swallows the padding. */
.draft-pick-card {
  display: block; padding: 16px 18px; border-radius: 10px;
  border: 1px solid var(--line); background: #FCFBF8;
  text-decoration: none; color: var(--ink);
}
.draft-pick-card:hover { border-color: var(--brass); background: #F7F5F0; }
.draft-pick-card-title { display: block; font-weight: 600; margin-bottom: 4px; }
.draft-pick-card-note { display: block; color: var(--ink-soft); font-size: 13px; }
/* THE PICK CARD IS THE LOUD ONE. This screen exists to ask a question, and
   a screen whose strongest control is "not now" has given up on being
   answered. */
.draft-pick-card-go {
  border-color: var(--pine); background: var(--pine); color: #fff;
}
.draft-pick-card-go:hover { background: var(--pine-dark); border-color: var(--pine-dark); }
.draft-pick-card-go .draft-pick-card-note { color: rgba(255, 255, 255, 0.82); }

/* The month cards. minmax(0, 1fr) for the reason every grid here carries
   it: a bare 1fr is min-content-sized and a long month name pushes the
   track. */
.draft-month-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 170px), 1fr));
  gap: 10px; margin: 0 0 4px;
}
.draft-month {
  display: block; padding: 12px 14px; border-radius: 9px;
  border: 1px solid var(--line); background: #FCFBF8; cursor: pointer;
}
.draft-month:hover { border-color: var(--brass); }
/* :has() ON THE CHECKED RADIO, so the selected card is visible without a
   line of JavaScript. Browsers without :has() fall back to the radio's own
   dot, which is still in the card and still tells the truth - the styling
   is the enhancement, not the answer. */
.draft-month:has(input:checked) { border-color: var(--pine); background: #EDF3EE; }
/* A TAKEN MONTH IS SHOWN AND DISABLED, NEVER OMITTED. A list that silently
   loses August reads as a list with no August in it, and the branch head
   goes looking for a bug rather than for another month. */
.draft-month.is-taken { opacity: 0.5; cursor: default; background: #F4F2ED; }
.draft-month.is-taken:hover { border-color: var(--line); }
.draft-month-label { display: block; font-weight: 600; margin-bottom: 2px; }
.draft-month-dates { display: block; color: var(--ink-soft); font-size: 12.5px; }

.draft-pick-choice {
  margin: 0 0 14px; padding: 14px 16px; border-radius: 9px;
  border: 1px solid var(--tag-2); background: #EDF3EE;
  font-size: 15px; color: var(--pine-dark);
}
.draft-pick-nights {
  display: block; margin-top: 4px;
  font-family: 'IBM Plex Mono', monospace; font-size: 13px; color: var(--ink-soft);
}

/* The nudge on the calendar, for somebody who pressed Continue to Roomee.
   .notice-banner does the colours; this only adds the layout, which is
   .people-cap-note's - sentence takes the slack, button drops under it on a
   narrow screen rather than squeezing the text into a column. */
.draft-nudge {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 8px 14px;
}
.draft-nudge > span { flex: 1 1 260px; }

/* Already claimed, on the branch head's date-picking screen (317m).
   Reuses .draft-picks whole - the rows, the round pill, the muted Skipped
   line - so this only spaces it off the fields above and gives it a rule to
   sit under, the way the settings card's own sections separate. */
.draft-taken { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--line); }
.draft-taken .draft-picks { margin-top: 6px; }
