/* Kings Bay Houses, site stylesheet.
   The surface of the editorial direction (warm paper, serif display,
   letterspaced uppercase labels) on a structure built for density:
   everything a caller needs is in the first screen, and photography is a
   capped strip that never occupies a viewport band.

   Self-contained. System fonts only. No external request of any kind, and
   Almost no JavaScript: the sticky header, the current-page marker and the nav
   are CSS and markup only. The single script, /js/theme.js, exists because a
   theme choice has to outlive a page navigation and only storage can do that.
   Everything still works with the script blocked; the theme just follows the
   system setting, which is the default anyway. */

/* ------------------------------------------------------------- tokens */

:root {
  color-scheme: light dark;

  /* Georgia is deliberately absent. It ships no lnum/tnum features, so the
     font-variant-numeric below has nothing to act on there and figures fall
     back to old style. Every face left in the stack can honour it, and every
     element carrying a number asks for it explicitly. */
  --serif: Charter, "Bitstream Charter", "Iowan Old Style", "Sitka Text", "Times New Roman", serif;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Arial, sans-serif;

  /* The warm palette from the editorial direction. Two surfaces, not three:
     paper is the page, tint is every raised thing (panel, call strip, tinted
     band), so a card reads as a deliberate second ground rather than as white
     sitting on off-white. */
  --paper:        #fbf8f4;
  --tint-warm:    #f7ece5;
  --panel:        var(--tint-warm);
  --ink:          #2a2320;
  --body:         #4a423c;
  --muted:        #6d625a;
  /* Two rule weights, and only two. --rule is the hairline that separates
     rows inside a component: ledger rows, table rows, the cells of the fact
     grid. Round one used #e6dcd2 for that, which is 1.16:1 on paper and only
     really visible on a good screen; this is 1.84:1 there and on the tint.
     --rule-strong is structural: the outer edge of a panel, the fact grid, a
     control. It is 3.33:1 on the warm tint and 3.65:1 on paper. */
  --rule:         #c7b8aa;
  --rule-strong:  #8f7f70;
  --accent:       #c0563a;
  --accent-ink:   #a8452c;
  --dark:         #241f1c;
  --on-dark:      #f2ebe4;
  --on-dark-muted:#c3b6ab;
  /* Not a brand colour on purpose: the accent is also every link and every
     underline, so a focus ring painted in it is invisible on the thing it is
     marking. 6.05:1 on paper, 8.2:1 on the dark paper. */
  --focus:        #0b5fb0;
  --focus-dark:   #7fb2f0;

  /* Layout. Tight outside, generous inside: the outer gutter and the gaps
     between blocks are small, the padding within a panel is not. */
  --maxw: 76rem;
  --gut: clamp(1rem, 3vw, 2rem);
  --pad: clamp(1.15rem, 2.4vw, 1.7rem);   /* internal panel padding */
  --measure: 36rem;                        /* about 68 characters at --fs-md */

  /* Spacing. Six steps on a 4px base, and every component pad is drawn from
     them. The only values off the ramp are sub-4px optical shims, marked
     where they occur: a baseline nudge is not a spacing decision. */
  --s-1: .25rem;
  --s-2: .5rem;
  --s-3: .75rem;
  --s-4: 1.25rem;
  --s-5: 2rem;
  --s-6: clamp(2.25rem, 4.5vw, 3.25rem);

  /* Type. Restrained sizes: hierarchy is carried by weight, letterspacing
     and hairlines, not by scale. Nothing on the page is set at a size that
     is not on this ramp, and the order is fixed: h1 42, h2 and the telephone
     number 24, a figure 20, the opening paragraph 17, body 16. A section
     heading and the number are the two things allowed to be large. */
  --fs-micro: .75rem;    /* 12 - uppercase micro labels, table heads */
  --fs-xs:    .8125rem;  /* 13 - credits, notes, captions, navigation */
  --fs-sm:    .875rem;   /* 14 - table cells, secondary body */
  --fs-base:  1rem;      /* 16 - body copy */
  --fs-md:    1.0625rem; /* 17 - the opening paragraph */
  --fs-lg:    1.1875rem; /* 19 - deck, wordmark */
  --fs-stat:  1.25rem;   /* 20 - key figures, footer wordmark */
  --fs-h2:    1.5rem;    /* 24 - section headings */
  --fs-tel:   1.5rem;    /* 24 - the telephone number in the office panel */
  --fs-h1:    clamp(1.9rem, 3.4vw, 2.6rem);  /* 30 to 42 - the one fluid step */

  /* One tracking value for the label tier. Label roles are told apart by
     colour and by the rule under them, never by a different tracking. */
  --track-label: .12em;
}

/* Dark palette, declared twice on purpose.
   1. Under the media query, but skipped when the visitor has explicitly chosen
      light, so their choice beats the system setting.
   2. Under [data-theme="dark"], so choosing dark beats a light system setting.
   The tokens are duplicated because CSS has no way to name a block and reuse
   it; keep the two lists identical. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper:        #191512;
    --tint-warm:    #2a221d;
    --ink:          #f2ebe4;
    --body:         #d9cfc5;
    --muted:        #ac9f94;
    --rule:         #4d4038;   /* 1.82:1 on the dark paper, matching light */
    --rule-strong:  #84735f;   /* 3.42:1 on the dark warm tint */
    --accent:       #e08a6c;
    --accent-ink:   #eb9d80;
    --dark:         #120f0d;
    --on-dark:      #f2ebe4;
    --on-dark-muted:#b9ada2;
    --focus:        var(--focus-dark);
  }
}

:root[data-theme="dark"] {
    --paper:        #191512;
    --tint-warm:    #2a221d;
    --ink:          #f2ebe4;
    --body:         #d9cfc5;
    --muted:        #ac9f94;
    --rule:         #4d4038;   /* 1.82:1 on the dark paper, matching light */
    --rule-strong:  #84735f;   /* 3.42:1 on the dark warm tint */
    --accent:       #e08a6c;
    --accent-ink:   #eb9d80;
    --dark:         #120f0d;
    --on-dark:      #f2ebe4;
    --on-dark-muted:#b9ada2;
    --focus:        var(--focus-dark);
}

/* Tell the browser which way form controls, scrollbars and the like should go
   once a choice has been made, otherwise they keep following the system. */
:root[data-theme="dark"]  { color-scheme: dark; }
:root[data-theme="light"] { color-scheme: light; }

/* --------------------------------------------------------------- base */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--body);
  font-family: var(--sans);
  font-size: var(--fs-base);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-wrap: break-word;
}

img { max-width: 100%; height: auto; display: block; }

h1, h2 {
  font-family: var(--serif);
  color: var(--ink);
  font-weight: 600;
  line-height: 1.2;
  margin: 0 0 .5em;
  text-wrap: balance;
  font-variant-numeric: lining-nums tabular-nums;
  font-feature-settings: "lnum" 1, "tnum" 1;
}
h1 { font-size: var(--fs-h1); letter-spacing: -.022em; line-height: 1.08; }
h2 { font-size: var(--fs-h2); letter-spacing: -.012em; }

p { margin: 0 0 .9em; }
p:last-child { margin-bottom: 0; }

a { color: var(--accent-ink); text-underline-offset: 2px; text-decoration-thickness: 1px; }
a:hover { color: var(--ink); }

/* Square, like everything else in the file. */
:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 0; }

.num {
  font-variant-numeric: lining-nums tabular-nums;
  font-feature-settings: "lnum" 1, "tnum" 1;
}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--rule-strong);
  padding: var(--s-3) var(--s-4); text-decoration: none; font-weight: 600;
}
.skip:focus { left: .75rem; top: .75rem; }

main:focus { outline: none; }

/* ------------------------------------------------------------- layout */

.wrap {
  width: 100%;
  max-width: calc(var(--maxw) + var(--gut) * 2);
  margin-inline: auto;
  padding-inline: var(--gut);
}

.sect { padding-block: var(--s-6); }
.sect--tight { padding-block: var(--s-5); }
.sect--tint { background: var(--tint-warm); }
.sect--ruled { border-top: 1px solid var(--rule-strong); }

/* The label tier: 12px, uppercase, --track-label, 700. Everything in it is
   set at those four values. What separates a section label from a data label
   is colour and the rule beneath it, not size and not tracking:
     section label   accent ink, 2px accent rule under it (.panel__hd, caption)
     standing label  accent ink, no rule (.eyebrow)
     data label      --muted, no rule (.dl dt, .keyfacts dt, thead th)
   The one label that is deliberately not in the tier is the navigation,
   below, which is a step larger so that it reads as the way through the
   site rather than as metadata. */
.eyebrow {
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--accent-ink);
  margin: 0 0 var(--s-3);
}

.deck {
  font-family: var(--serif);
  font-size: var(--fs-lg);
  line-height: 1.42;
  color: var(--ink);
  max-width: var(--measure);
  margin: 0 0 var(--s-4);
  font-variant-numeric: lining-nums tabular-nums;
  font-feature-settings: "lnum" 1, "tnum" 1;
}

/* Deck and lead share one right edge. The fact grid below them is the single
   deliberate break out to the full column. */
.lead { font-size: var(--fs-md); max-width: var(--measure); }

.note { font-size: var(--fs-xs); line-height: 1.55; color: var(--muted); }
.note strong { color: var(--ink); }

.credit { font-size: var(--fs-xs); line-height: 1.5; color: var(--muted); margin: 0; }
.credit a { color: var(--muted); }
.credit a:hover { color: var(--ink); }

.head-block { max-width: var(--measure); margin-bottom: var(--s-4); }

/* ------------------------------------------------------------- header */

.flag {
  margin: 0;
  background: var(--dark);
  color: var(--on-dark);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  text-align: center;
  padding: var(--s-1) var(--s-4);
}
/* The band sits on --dark, so the link inside it cannot use the accent: that
   is a 2.1:1 pair there. Underlined and in the on-dark ink instead. */
.flag a { color: var(--on-dark); }
.flag a:hover { color: #fff; }
.flag :focus-visible { outline-color: var(--focus-dark); }

.head {
  background: var(--paper);
  border-bottom: 1px solid var(--rule-strong);
}
.head__in {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3) var(--s-5);
  flex-wrap: wrap;
  padding-block: var(--s-2);
}

.brand { display: flex; align-items: flex-start; gap: var(--s-2); text-decoration: none; color: var(--ink); margin-right: auto; }
.brand__mark {
  width: 34px; height: 34px; flex: none;
  margin-top: .1rem;   /* optical: aligns the square to the wordmark, not to
                          the centre of the two-line lockup */
  display: grid; place-items: center;
  background: var(--accent-ink);
  color: var(--paper);   /* not #fff: on the dark scheme --accent-ink is a
                            light terracotta and white on it is 2.17:1 */
  font-family: var(--serif);
  font-weight: 700;
  font-size: var(--fs-base);   /* 47% of the 34px square, the proportion the
                                  shipped favicon sets its own KB at */
  letter-spacing: .02em;
}
.brand__name {
  display: block;
  font-family: var(--serif);
  font-size: var(--fs-lg);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -.012em;
}
.brand__sub {
  display: block;
  margin-top: .15rem;   /* optical: a lockup shim, not a spacing step */
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--muted);
}
.brand:hover .brand__name { color: var(--accent-ink); }

/* Navigation: the one label on the page that is not 12px. A step larger and
   a touch wider tracked than the label tier, so it reads as navigation and
   not as a caption. An accent hairline marks the current page. No script and
   no disclosure widget, so nothing flashes open and nothing depends on
   JavaScript. */
.nav { order: 3; width: 100%; border-top: 1px solid var(--rule); padding-top: var(--s-1); }
.nav__list {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 0 var(--s-4); list-style: none; margin: 0; padding: 0;
}
.nav__list a,
.nav__list .nav__soon {
  display: inline-flex; align-items: center; min-height: 44px;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--body);
  text-decoration: none;
  border-bottom: 2px solid transparent;
}
.nav__list a:hover { color: var(--accent-ink); border-bottom-color: var(--rule-strong); }
.nav__list a[aria-current="page"] { color: var(--accent-ink); border-bottom-color: var(--accent); }
/* Not links, and they do not pretend to be: no hover, no pointer. */
.nav__list .nav__soon { color: var(--muted); font-weight: 600; cursor: default; }
.nav__note { margin: 0 0 var(--s-1); font-size: var(--fs-xs); color: var(--muted); }

.head__tel {
  display: inline-flex; align-items: center; gap: var(--s-2);
  min-height: 44px;
  color: var(--ink);
  text-decoration: none;
  font-size: var(--fs-base);
  font-weight: 700;
  white-space: nowrap;
  font-variant-numeric: lining-nums tabular-nums;
  font-feature-settings: "lnum" 1, "tnum" 1;
}
.head__tel .ico { width: 1rem; height: 1rem; fill: var(--accent-ink); flex: none; }
.head__tel .txt { border-bottom: 2px solid var(--accent); padding-bottom: 1px; }  /* optical: 1px of air over the rule */
.head__tel:hover { color: var(--accent-ink); }

@media (min-width: 64em) {
  /* One row here: the brand at the left edge, the navigation immediately
     after it, and the telephone number anchored hard right in its own ruled
     cell. Round one left two competing auto margins in this bar, which
     parked the number at 38% across, aligned to nothing. */
  .head__in { flex-wrap: nowrap; align-items: center; }
  .brand { margin-right: 0; }
  .nav { order: 0; width: auto; border-top: 0; padding-top: 0; }
  .nav__list { flex-wrap: nowrap; }
  .nav__list a, .nav__list .nav__soon { min-height: 44px; }
  .nav__note { display: none; }
  .head__tel {
    order: 1;
    margin-left: auto;
    border-left: 1px solid var(--rule);
    padding-left: var(--s-4);
  }
  /* Pinned, because the number is the point of the direction and a flick of
     the wheel should not take it away. The header is a single 56px row at
     this width. Below it the header is three stacked rows and pinning it
     would cost a third of a phone screen, so there it scrolls with the page
     and the number stays where a phone reader starts: the top. */
  .head { position: sticky; top: 0; z-index: 50; }
  /* WCAG 2.2 SC 2.4.11 Focus Not Obscured: the sticky bar is about 61px, so
     anything scrolled flush to the top would sit under it. Keep focus clear. */
  :target, a:focus-visible, button:focus-visible, [tabindex]:focus-visible,
  input:focus-visible, summary:focus-visible { scroll-margin-top: 5rem; }
}

/* --------------------------------------------------------------- fold */

/* Everything a caller needs, above the fold on a 1440x900 laptop: the name,
   what the place is, the office address, the telephone number, the four
   building addresses and the first substantive paragraph.

   Three areas, not two, so the photograph can be placed rather than
   inherited. On a wide screen it sits under the copy in the left column; in
   one column it goes last, after the office panel and the four addresses,
   because the rule is that photography is what comes after the facts and a
   phone must not be the one place where that is reversed. */
.fold { padding-block: var(--s-5); }
.fold__grid {
  display: grid;
  gap: var(--s-5) clamp(1.5rem, 3vw, 2.75rem);
  grid-template-areas: "lead" "aside" "photo";
}
.fold__lead  { grid-area: lead; }
.fold__aside { grid-area: aside; display: grid; gap: var(--s-4); align-content: start; }
.fold__photo { grid-area: photo; }
@media (min-width: 64em) {
  .fold__grid {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    grid-template-areas:
      "lead  aside"
      "photo aside";
    row-gap: var(--s-4);
    align-items: start;
  }
}
.fold h1 { margin-bottom: .3em; }

/* Key figures: a hairline modular grid, not a row of oversized numerals.
   The cell divisions are hairlines and only the outer edge is structural,
   so the grid is the quietest object above the fold rather than the
   heaviest. */
.keyfacts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;   /* not a space: this is the hairline itself, showing the
                 background through between the cells */
  margin: var(--s-4) 0 0;
  background: var(--rule);
  border: 1px solid var(--rule-strong);
}
@media (min-width: 34em) {
  .keyfacts { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .keyfacts--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.keyfacts > div { background: var(--paper); padding: var(--s-3); }
.keyfacts dt {
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: var(--s-1);
}
.keyfacts dd {
  margin: 0;
  font-family: var(--serif);
  font-size: var(--fs-stat);
  line-height: 1.1;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -.015em;
  font-variant-numeric: lining-nums tabular-nums;
  font-feature-settings: "lnum" 1, "tnum" 1;
}
.keyfacts dd small {
  display: block; margin-top: var(--s-1);
  font-family: var(--sans); font-size: var(--fs-xs); font-weight: 400;
  letter-spacing: 0; color: var(--muted); line-height: 1.35;
}

/* -------------------------------------------------------------- panel */

.panel {
  background: var(--panel);
  border: 1px solid var(--rule-strong);
  padding: var(--pad);
}
.panel__hd {
  font-family: var(--sans);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--accent-ink);
  margin: 0 0 var(--s-3);
  padding-bottom: var(--s-2);
  border-bottom: 2px solid var(--accent);
}

/* Definition list as a two-column ledger. */
.dl { margin: 0; }
.dl > div {
  display: grid;
  gap: var(--s-1) var(--s-3);
  padding: var(--s-3) 0;
  border-top: 1px solid var(--rule);
}
.dl > div:first-child { border-top: 0; padding-top: 0; }
.dl > div:last-child { padding-bottom: 0; }
@media (min-width: 26em) { .dl > div { grid-template-columns: 7.25rem minmax(0, 1fr); } }
.dl dt {
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--muted);
  padding-top: .18rem;   /* optical: sets the 12px label on the 14px baseline */
}
.dl dd { margin: 0; font-size: var(--fs-sm); color: var(--body); line-height: 1.45; }
.dl dd strong { color: var(--ink); font-weight: 600; }

/* Two ledgers side by side used to be two separate lists, whose hairlines
   stopped at the gutter and restarted at a different height whenever the
   two sides wrapped differently. One list in a two-column grid instead:
   both halves of a row share the same grid row, so every rule runs true
   across the full width, and the source order stays grouped for a phone. */
.dl--split > div:first-child { border-top: 1px solid var(--rule); padding-top: var(--s-3); }
@media (min-width: 60em) {
  .dl--split {
    display: grid;
    grid-auto-flow: column;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: repeat(4, auto);
    column-gap: clamp(1.5rem, 3vw, 2.75rem);
  }
  .dl--rows-3 { grid-template-rows: repeat(3, auto); }
  .dl--split > div:last-child { padding-bottom: var(--s-3); }
}

.tel-big {
  display: inline-flex; align-items: center;
  min-height: 44px;
  font-family: var(--serif);
  font-size: var(--fs-tel);
  font-weight: 600;
  line-height: 1;
  color: var(--ink);
  text-decoration: none;
  border-bottom: 2px solid var(--accent);
  white-space: nowrap;
  font-variant-numeric: lining-nums tabular-nums;
  font-feature-settings: "lnum" 1, "tnum" 1;
}
.tel-big:hover { color: var(--accent-ink); }

/* -------------------------------------------------------------- table */

/* position matters as much as overflow here: .sr-only is absolutely positioned,
   so without a containing block on this wrapper a visually hidden label inside a
   table that carries a min-width escapes the clip and stretches the DOCUMENT's
   scroll width. That put a horizontal scrollbar on the whole page at phone
   widths. Establish the containing block and the clip does its job. */
.tbl-wrap { overflow-x: auto; position: relative; }
.tbl {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
  text-align: left;
}
.tbl caption {
  text-align: left;
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--accent-ink);
  padding-bottom: var(--s-2);
  border-bottom: 2px solid var(--accent);
}
.tbl th, .tbl td { padding: var(--s-2) var(--s-4) var(--s-2) 0; border-top: 1px solid var(--rule); vertical-align: top; }
.tbl thead th {
  border-top: 0;
  padding-top: var(--s-2);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--muted);
}
.tbl tbody th, .tbl td {
  font-variant-numeric: lining-nums tabular-nums;
  font-feature-settings: "lnum" 1, "tnum" 1;
}
.tbl tbody th { font-weight: 600; color: var(--ink); }
.tbl td { color: var(--body); }
.tbl td:last-child, .tbl th:last-child { padding-right: 0; }
.tbl--spec tbody th { width: 12rem; white-space: normal; }

/* --------------------------------------------------------- photography */

/* Capped on purpose, and composed rather than left to the window: the crop
   is a fixed ratio at every size, so the same picture is framed the same way
   on a 1366 laptop and a 1920 monitor. A photograph is a band of context
   here, never a screenful. In one column it opens up to 3:2, because there
   a strip 150px tall is a smear rather than a picture. */
.strip { margin: 0; }
.strip img {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 5;
  object-fit: cover;
  object-position: 50% 100%;   /* the street-level band: brick, balconies and
                                  the trees on Batchelder Street */
  border: 1px solid var(--rule-strong);
}
@media (max-width: 40em) {
  .strip img { aspect-ratio: 3 / 2; }
}
/* The bay photograph is the one true panoramic on the site, so it is held to
   the width of the copy column and given a ratio close to the frame it was
   shot in. Run full width at the strip height it would be 6.4:1, which is a
   ribbon, not a picture. */
.strip--bay { max-width: 44rem; }
.strip--bay img { aspect-ratio: 5 / 2; object-position: 50% 35%; }
@media (max-width: 40em) {
  .strip--bay img { aspect-ratio: 3 / 2; }
}

.strip figcaption {
  display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-5);
  margin-top: var(--s-2);
  font-size: var(--fs-xs);
  line-height: 1.5;
  color: var(--muted);
}
.strip figcaption p { margin: 0; }
.strip figcaption a { color: var(--muted); }
.strip figcaption a:hover { color: var(--ink); }

/* On a wide screen it is a plate with its caption set beside it, on the same
   7/5 split as the fold, so the picture is 704px like the fold photograph and
   the band is filled rather than trailing off into empty page. */
@media (min-width: 60em) {
  .strip--bay {
    max-width: none;
    display: grid;
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    gap: 0 clamp(1.5rem, 3vw, 2.75rem);
    align-items: start;
  }
  .strip--bay figcaption { display: block; margin-top: 0; }
  .strip--bay figcaption p + p { margin-top: var(--s-3); }
}

/* The inset is the one portrait frame on the site, and it is portrait for a
   reason: the box is narrower than the source, so object-fit crops it
   sideways onto the facade instead of down onto the sky. It is also the only
   crop that fills the column beside a long table rather than leaving a void
   under a letterbox. Below the two-column breakpoint the box is full width
   again, where a 3:4 picture would be half a screen tall, so the ratio only
   applies where the column is 19rem. */
.inset { margin: 0; }
.inset img { width: 100%; border: 1px solid var(--rule-strong); }
@media (min-width: 60em) {
  .inset img { aspect-ratio: 3 / 4; height: auto; object-fit: cover; object-position: 0% 50%; }
}
.inset figcaption { margin-top: var(--s-2); font-size: var(--fs-xs); line-height: 1.5; color: var(--muted); }
.inset figcaption a { color: var(--muted); }
.inset figcaption a:hover { color: var(--ink); }

/* ------------------------------------------------------------ columns */

.cols { display: grid; gap: var(--s-5) clamp(1.5rem, 3vw, 2.75rem); }
@media (min-width: 60em) {
  .cols { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); align-items: start; }
  .cols--media { grid-template-columns: minmax(0, 1fr) minmax(0, 19rem); }
}
.cols__body { max-width: var(--measure); }

/* Terms: hairline separated, term and definition stacked tight. Same rhythm
   as the ledger above, because it is the same idea. */
.terms { margin: 0; }
.terms > div { padding: var(--s-3) 0; border-top: 1px solid var(--rule); }
.terms > div:first-child { border-top: 0; padding-top: 0; }
.terms dt {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink);
  margin-bottom: var(--s-1);
}
.terms dd {
  margin: 0; font-size: var(--fs-sm); color: var(--body); line-height: 1.5;
  font-variant-numeric: lining-nums tabular-nums;
  font-feature-settings: "lnum" 1, "tnum" 1;
}

/* ---------------------------------------------------------- call strip */

.callstrip {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3) var(--s-5);
  justify-content: space-between;
  background: var(--panel);
  border: 1px solid var(--rule-strong);
  padding: var(--pad);
}
.callstrip__text { max-width: var(--measure); }
.callstrip h2 { margin-bottom: .25em; }
.callstrip p { font-size: var(--fs-sm); margin: 0; }
.callstrip__act { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3) var(--s-4); }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  min-height: 44px;
  padding: var(--s-2) var(--s-4);
  background: var(--accent-ink);
  border: 1px solid var(--accent-ink);
  color: var(--paper);
  text-decoration: none;
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  transition: background-color .18s ease, border-color .18s ease, color .18s ease;
}
.btn:hover { background: var(--ink); border-color: var(--ink); color: var(--paper); }

.arrow {
  display: inline-flex; align-items: center; gap: var(--s-2);
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink);
  text-decoration: none;
  border-bottom: 2px solid var(--accent);
  padding-bottom: .1rem;   /* optical: clears the descenders above the rule */
}
.arrow:hover { color: var(--accent-ink); }

/* -------------------------------------------------------------- footer */

.foot {
  background: var(--dark);
  color: var(--on-dark-muted);
  font-size: var(--fs-sm);
  margin-top: var(--s-6);
}
.foot a { color: var(--on-dark); }
.foot a:hover { color: #fff; }
.foot :focus-visible { outline-color: var(--focus-dark); }
.foot h2 {
  font-family: var(--sans);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--on-dark-muted);
  margin: 0 0 var(--s-3);
}
.foot__grid {
  display: grid;
  gap: var(--s-5) clamp(1.5rem, 3vw, 2.75rem);
  padding-block: var(--s-5);
}
@media (min-width: 52em) {
  .foot__grid { grid-template-columns: minmax(0, 1.25fr) minmax(0, .8fr) minmax(0, 1.35fr); }
}
.foot__mark { font-family: var(--serif); font-size: var(--fs-stat); color: var(--on-dark); margin: 0 0 var(--s-2); }
.foot ul { list-style: none; margin: 0; padding: 0; }
.foot li { margin-bottom: 0; }
.foot__links a { display: inline-flex; align-items: center; min-height: 44px; text-decoration: none; }
.foot__links a:hover { text-decoration: underline; }
/* The one tap-to-call that was not a target: 44px here as everywhere else. */
.foot__tel { display: inline-flex; align-items: center; min-height: 44px; }
.foot .credit { color: var(--on-dark-muted); margin-top: var(--s-3); }
.foot .credit a { color: var(--on-dark-muted); }
.foot .credit a:hover { color: #fff; }
.foot__notice { padding-block: var(--s-4) 0; border-top: 1px solid var(--rule-strong); }
.foot__notice p { margin: 0; font-size: var(--fs-xs); line-height: 1.55; }
.foot__bar {
  padding-block: var(--s-4) var(--s-5);
  font-size: var(--fs-xs);
  display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-5); justify-content: space-between;
}
.foot__bar p { margin: 0; }

/* ----------------------------------------------------------- utilities */

/* Last in the file on purpose: these four adjustments must win over the
   component rules they are applied to (.strip sets margin: 0). */
.u-mt-3 { margin-top: var(--s-3); }
.u-mt-4 { margin-top: var(--s-4); }
.u-mt-5 { margin-top: var(--s-5); }
.u-lead-note { max-width: var(--measure); margin-bottom: var(--s-4); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

/* The table wrappers scroll horizontally, so they carry tabindex="0" for
   keyboard users (SC 2.1.1). Give that focus a visible indicator. */
.tbl-wrap:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }

/* ------------------------------------------------- theme switch */

/* Hidden until /js/theme.js proves scripting works. A button that cannot do
   anything is worse than no button, and with the script blocked the theme
   simply follows the system setting, which is the sensible default. */
.themetoggle { display: none; }
:root.has-js .themetoggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  border: 1px solid var(--rule-strong);
  border-radius: 6px;
  background: transparent;
  color: var(--body);
  cursor: pointer;
}
.themetoggle:hover { color: var(--ink); border-color: var(--muted); }
.themetoggle:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
.themetoggle .ico { width: 1.05rem; height: 1.05rem; fill: currentColor; }

/* One icon at a time. data-to is the theme the button switches TO, so a
   visitor in dark mode sees a sun: press it to get light. */
.themetoggle .i-light,
.themetoggle .i-dark { display: none; }
.themetoggle[data-to="light"] .i-light,
.themetoggle[data-to="dark"]  .i-dark { display: block; }

/* -------------------------------------------- the construction schedule */

/* A printed Gantt chart re-set for the web, with more than one way to read it.
 *
 * ONE TABLE, SEVERAL VIEWS. There is a single semantic table per building and
 * it never changes: work name, first month, last month, and a timeline cell.
 * A view is a CSS variant selected by data-view on .sched-scope. Nothing is
 * duplicated in the markup, no view is built by script, and with scripting off
 * the default view is complete and the picker is hidden.
 *
 * To add a view: give it a name, add a button in the picker, and write one
 * block below. The month ruler is already in the markup for every view to use.
 *
 * Positions come from data-from and data-span attributes rather than a style
 * attribute, because style-src in _headers has no 'unsafe-inline'. The
 * attribute rules are at the end of this section.
 */

/* ----------------------------------------------------- the colour groups */

/* Six groups, not fifteen. The paper schedule gave nearly every row its own
   colour, but three rows shared one tan and gold/yellow, the two violets and
   the two pale blues were each hard to tell apart, so the colours carried no
   information. These six group the work by trade, which is what actually runs
   in blocks down a building.

   The hues are the documented categorical palette, slots 1 to 6, in slot order.
   The order is not decoration: it is the colour-vision safety mechanism, and
   the groups are numbered in the order they first appear down a building, so
   every vertically neighbouring pair of bars is a pair the validator checked.
   Validated in both themes against the track colour the bars sit on
   (#f7ece5 light, #2a221d dark), worst adjacent CVD dE 9.1 light and 8.4 dark.

   Light mode reports a contrast WARN: four of the six sit below 3:1 on the warm
   track. The relief the method asks for is present and is not optional here -
   every bar is in a labelled row that also spells out its first and last month
   in words, and the whole chart is a table. Colour groups; it never identifies.

   Do not edit a value here without re-running the validator for BOTH modes. */
:root {
  --g1: #2a78d6;   /* facade, balconies, roof */
  --g2: #eb6834;   /* heating and electrical */
  --g3: #1baf7a;   /* plumbing and abatement */
  --g4: #eda100;   /* inside apartments */
  --g5: #e87ba4;   /* windows and store fronts */
  --g6: #008300;   /* grounds */
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --g1: #3987e5;
    --g2: #d95926;
    --g3: #199e70;
    --g4: #c98500;
    --g5: #d55181;
    --g6: #008300;
  }
}
:root[data-theme="dark"] {
  --g1: #3987e5;
  --g2: #d95926;
  --g3: #199e70;
  --g4: #c98500;
  --g5: #d55181;
  --g6: #008300;
}

/* ------------------------------------------------------ page structure */

/* The schedule IS the page, so it starts as high as the header allows: a name,
   one paragraph, the controls, then the tables. Everything that explains or
   qualifies it, including the office details and the tentative-dates notice,
   now sits below them rather than beside the title. */
.sched-top { padding-block: var(--s-5) var(--s-6); }
.sched-masthead { margin-bottom: var(--s-5); }
.sched-masthead h1 { margin-bottom: .25em; }
.sched-masthead .deck { margin-bottom: 0; max-width: var(--measure); }

/* Once there is room, the standfirst moves alongside the title instead of
   under it. That uses the width the title leaves empty and, more to the point,
   takes its three lines out of the stack above the tables.

   The paragraph still carries a measure. Letting it run the full 76rem would
   put about 140 characters on a line, and the reason every other standfirst on
   this site is capped at 36rem is that the eye loses the start of the next line
   long before that. 44rem is the compromise: wider than the house measure,
   still short enough to track. */
@media (min-width: 64em) {
  .sched-masthead {
    display: grid;
    grid-template-columns: auto minmax(18rem, 1fr);
    column-gap: clamp(1.5rem, 3vw, 2.75rem);
    align-items: end;
  }
  .sched-masthead .eyebrow { grid-column: 1; grid-row: 1; }
  .sched-masthead h1 { grid-column: 1; grid-row: 2; margin-bottom: 0; }
  .sched-masthead .deck {
    grid-column: 2;
    grid-row: 1 / 3;
    align-self: end;
    max-width: 44rem;
  }
}

/* The two notices that used to sit in the first screen, side by side once
   there is room for them. */
.panelgrid { display: grid; gap: var(--s-4); align-items: start; }
@media (min-width: 60em) {
  .panelgrid { grid-template-columns: 1fr 1fr; }
}

/* ------------------------------------------------ the table, every view */

.sched { margin-bottom: var(--s-6); }
.sched:last-of-type { margin-bottom: 0; }
.sched[hidden] { display: none; }

.sched__hd { margin-bottom: var(--s-3); }
.sched__hd h3 {
  font-family: var(--serif);
  font-size: var(--fs-stat);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -.012em;
  color: var(--ink);
  margin: 0 0 .15em;
  font-variant-numeric: lining-nums tabular-nums;
  font-feature-settings: "lnum" 1, "tnum" 1;
}
.sched__meta { margin: 0; font-size: var(--fs-xs); color: var(--muted); }

/* Fixed layout, or the timeline column takes what it likes and the work names
   shred into one word per line. The widths are set on the head row and every
   body row inherits them; each view sets its own. */
.sched__tbl { table-layout: fixed; }
/* The bar carries the group colour, but it lives at the far side of the row and
   the list view has no timeline at all, so the same colour also sits as a small
   square in front of the work name.

   A hanging indent, not an absolutely positioned dot. The square is an ordinary
   inline-block in the text flow, so it rides the text's own baseline and stays
   aligned whatever the row's font size or line height; a negative text-indent
   equal to its width plus its margin pulls the first line back into the padding,
   so a name that wraps still lines up under itself. The earlier version offset
   the square from the cell's padding box instead, which left it sitting about
   five pixels above the words. */
.sched__tbl th[scope="row"] {
  padding-right: var(--s-3);
  padding-left: 1.15rem;
  text-indent: -1.15rem;
}
.sched__dot {
  display: inline-block;
  width: .62rem;
  height: .62rem;
  margin-right: .53rem;    /* .62 + .53 = the 1.15rem the first line hangs by */
  vertical-align: middle;
  /* middle aligns on the baseline plus half the x-height, which for this stack
     leaves the square about 2px under the centre of the line. Optical shim, in
     em so it tracks the type size; measured, not guessed. */
  position: relative;
  top: -.14em;
  text-indent: 0;
  background: var(--rule-strong);
}
.sched__row[data-group="1"] .sched__dot { background: var(--g1); }
.sched__row[data-group="2"] .sched__dot { background: var(--g2); }
.sched__row[data-group="3"] .sched__dot { background: var(--g3); }
.sched__row[data-group="4"] .sched__dot { background: var(--g4); }
.sched__row[data-group="5"] .sched__dot { background: var(--g5); }
.sched__row[data-group="6"] .sched__dot { background: var(--g6); }
.sched__tbl th[scope="row"] small {
  display: block;
  text-indent: 0;
  font-weight: 400;
  font-size: var(--fs-xs);
  line-height: 1.35;
  color: var(--muted);
}
.sched__tbl td { white-space: nowrap; font-variant-numeric: lining-nums tabular-nums; }
.sched__row--none td { color: var(--muted); font-style: italic; }
.sched__cell { padding-right: 0; }

/* The year ruler. The three years are not equal: the sheet starts in March, so
   2026 is ten columns wide and the other two are twelve. Flex weights carry
   that, and the month ruler below divides the same width into 34. */
.sched__scale { display: flex; width: 100%; min-width: 12rem; }
.sched__yr {
  font-size: var(--fs-micro);
  letter-spacing: var(--track-label);
  color: var(--muted);
  padding-left: .3rem;
}
.sched__yr--a { flex: 10; }
.sched__yr--b { flex: 12; border-left: 1px solid var(--rule); }
.sched__yr--c { flex: 12; border-left: 1px solid var(--rule); }

/* Hidden by default; the month view reveals it. */
.sched__ruler { display: none; }

.sched__track {
  position: relative;
  display: block;
  height: .7rem;
  margin-top: .28rem;         /* optical: sits the bar on the cap line of the row */
  background: var(--tint-warm);
  border: 1px solid var(--rule);
}
/* The two year boundaries, at ten and twenty-two months of thirty-four. */
.sched__track::before,
.sched__track::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--rule-strong);
}
.sched__track::before { left: calc(10 / 34 * 100%); }
.sched__track::after  { left: calc(22 / 34 * 100%); }

.sched__bar {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--a) / 34 * 100%);
  width: calc(var(--b) / 34 * 100%);
  background: var(--accent);
}
.sched__row[data-group="1"] .sched__bar { background: var(--g1); }
.sched__row[data-group="2"] .sched__bar { background: var(--g2); }
.sched__row[data-group="3"] .sched__bar { background: var(--g3); }
.sched__row[data-group="4"] .sched__bar { background: var(--g4); }
.sched__row[data-group="5"] .sched__bar { background: var(--g5); }
.sched__row[data-group="6"] .sched__bar { background: var(--g6); }

/* The boiler row is the one line on the sheet that is not about its own
   building, and it is also the one place where a bar in group 3 ends up
   directly above a bar in group 5, a pair the palette order does not cover.
   Hatching it settles both: it marks the row as the shared one, and it tells
   the two apart by texture rather than by hue. */
/* Specificity, not source order, decides here: the group rules above use the
   background shorthand, which resets background-image, and they carry three
   compound selectors. This one has to carry at least as many or the hatch is
   silently discarded. */
.sched__row[data-group] .sched__bar[data-flag="boilers"] {
  background-image: repeating-linear-gradient(
    45deg,
    rgba(255, 255, 255, .75) 0 3px,
    rgba(255, 255, 255, 0) 3px 7px
  );
}

/* Written in by /js/schedule.js, and only when today falls inside the chart. */
.sched__now {
  position: absolute;
  top: -3px;
  bottom: -3px;
  left: calc((var(--a) + .5) / 34 * 100%);
  width: 2px;
  margin-left: -1px;
  background: var(--ink);
}

/* ------------------------------------------------------ view: compact ---
 * The default. Years marked, months implied. Fits a laptop without scrolling
 * and is the one that survives being printed. */
.sched-scope[data-view="compact"] .sched__tbl { min-width: 46rem; }
.sched-scope[data-view="compact"] .sched__tbl thead th:nth-child(1) { width: 26%; }
.sched-scope[data-view="compact"] .sched__tbl thead th:nth-child(2),
.sched-scope[data-view="compact"] .sched__tbl thead th:nth-child(3) { width: 15%; }
.sched-scope[data-view="compact"] .sched__tbl thead th:nth-child(4) { width: 44%; }

/* ------------------------------------------------------- view: months ---
 * Every one of the thirty-four months named and ruled. Deliberately wider than
 * any screen: the table wrapper scrolls sideways, which is the honest way to
 * show three years at month resolution. */
.sched-scope[data-view="months"] .sched__tbl { min-width: 112rem; }
.sched-scope[data-view="months"] .sched__tbl thead th:nth-child(1) { width: 16rem; }
.sched-scope[data-view="months"] .sched__tbl thead th:nth-child(2),
.sched-scope[data-view="months"] .sched__tbl thead th:nth-child(3) { width: 8.5rem; }

.sched-scope[data-view="months"] .sched__ruler {
  display: flex;
  width: 100%;
  margin-top: .25rem;
}
.sched-scope[data-view="months"] .sched__ruler > span {
  flex: 1 1 0;
  min-width: 0;
  padding: .15rem 0;
  border-left: 1px solid var(--rule);
  font-size: var(--fs-micro);
  font-weight: 400;
  text-align: center;
  color: var(--muted);
  overflow: hidden;
}
/* January, and the March the chart opens on. */
.sched-scope[data-view="months"] .sched__ruler > span.is-year {
  border-left-color: var(--rule-strong);
  color: var(--ink);
  font-weight: 700;
}
/* One rule per month behind the bars. */
.sched-scope[data-view="months"] .sched__track {
  background-image: repeating-linear-gradient(
    to right,
    var(--rule) 0, var(--rule) 1px,
    transparent 1px, transparent calc(100% / 34)
  );
}
.sched-scope[data-view="months"] .sched__track { height: .85rem; }

/* Scrolling three years sideways is only worth doing if you can still see which
   row you are on, so the work name pins to the left edge of the wrapper. Only
   from 48em: on a phone a 16rem sticky column would leave almost no timeline
   beside it, and the list view is the better answer at that size anyway. */
@media (min-width: 48em) {
  .sched-scope[data-view="months"] .sched__tbl thead th:nth-child(1),
  .sched-scope[data-view="months"] .sched__tbl th[scope="row"] {
    position: sticky;
    left: 0;
    z-index: 2;
    background: var(--paper);
    box-shadow: 1px 0 0 var(--rule);
  }
}

/* --------------------------------------------------------- view: list ---
 * No timeline at all. Three text columns that fit the narrowest phone with no
 * sideways scrolling, which is what someone standing in the lobby wants. */
.sched-scope[data-view="list"] .sched__tbl { min-width: 0; table-layout: auto; }
.sched-scope[data-view="list"] .sched__cell,
.sched-scope[data-view="list"] .sched__tbl thead th:nth-child(4) { display: none; }
.sched-scope[data-view="list"] .sched__legend { display: none; }
.sched-scope[data-view="list"] .sched__tbl td { white-space: normal; }


/* --------------------------------------------------------- the controls */

.ctrls { display: grid; gap: var(--s-4); margin-bottom: var(--s-4); }
@media (min-width: 64em) {
  .ctrls { grid-template-columns: minmax(0, 1fr) auto; align-items: start; }
}
.ctrls .filters fieldset { margin-bottom: 0; height: 100%; }

/* Hidden until a script proves it can do something, the same contract the
   theme switch keeps. With scripting off all four buildings are in the page in
   the compact view and nothing is missing, so there is nothing to apologise
   for. */
/* Gated on sched-js, which only /js/schedule.js sets. The theme switch sets
   has-js, and gating on that meant a page where theme.js loaded but schedule.js
   did not showed a filter and a view picker that did nothing at all. Each
   control is revealed by the script that makes it work, and by no other. */
.filters, .viewpick { display: none; }
:root.sched-js .filters,
:root.sched-js .viewpick { display: block; }

.filters fieldset {
  margin: 0 0 var(--s-5);
  padding: var(--pad);
  border: 1px solid var(--rule-strong);
  background: var(--tint-warm);
}
.filters legend,
.viewpick__h {
  padding: 0 var(--s-2);
  margin: 0 0 0 calc(var(--s-2) * -1);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink);
}
.filters__row { display: flex; flex-wrap: wrap; gap: 0 var(--s-5); margin: 0; }
.filters__opt {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 44px;          /* WCAG 2.2 SC 2.5.8, and a fair thumb target */
}
.filters__opt input { width: 1.1rem; height: 1.1rem; accent-color: var(--accent); flex: none; }
.filters__opt label { font-size: var(--fs-sm); color: var(--ink); cursor: pointer; }
.filters__act {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin: var(--s-3) 0 0;
  padding-top: var(--s-3);
  border-top: 1px solid var(--rule);
}

/* Wide enough for the four buildings and the two buttons on one line, which
   takes a whole row out of the stack above the tables. The wrapper exists
   because a fieldset is a poor flex container: the legend is rendered outside
   the normal flow and browsers disagree about what happens to it. */
@media (min-width: 64em) {
  /* Tighter than the house panel padding: this box is the last thing between
     the reader and the schedule, so it earns its height rather than assuming
     it. */
  .filters fieldset { padding: var(--s-3) var(--s-4); }
  .filters__inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0 var(--s-4);
    flex-wrap: wrap;
  }
  .filters__row { gap: 0 var(--s-4); }
  /* flex: none and a nowrap label, so an option never breaks across two lines:
     it either sits on the row or moves to the next one whole. Both containers
     wrap, because at this width the four buildings and the two buttons only
     just fit beside the view picker, and forcing one line made the box overflow
     into it. */
  .filters__opt { flex: none; }
  .filters__opt label { white-space: nowrap; }
  .filters__act {
    /* flex: none, or the pair gets squeezed two pixels under what it needs and
       silently wraps onto a second line, doubling the height of the box. */
    flex: none;
    flex-wrap: nowrap;
    margin: 0;
    padding-top: 0;
    border-top: 0;
  }
}

/* The view picker. A segmented control, because the choices are exclusive and
   there are few enough to show all of them. */
.viewpick__h { margin-left: 0; padding-left: 0; margin-bottom: var(--s-2); }
.viewpick__set {
  display: flex;
  flex-wrap: wrap;
  margin: 0;
  border: 1px solid var(--rule-strong);
  background: var(--tint-warm);
}
.viewpick__btn {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  flex: 1 0 auto;
  min-height: 44px;
  padding: var(--s-2) var(--s-4);
  border: 0;
  border-left: 1px solid var(--rule);
  background: transparent;
  font: inherit;
  font-size: var(--fs-sm);
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}
.viewpick__btn:first-child { border-left: 0; }
.viewpick__btn small { font-size: var(--fs-micro); color: var(--muted); }
.viewpick__btn:hover { background: var(--paper); }
.viewpick__btn[aria-pressed="true"] { background: var(--ink); color: var(--paper); }
.viewpick__btn[aria-pressed="true"] small { color: var(--paper); }
.viewpick__btn:focus-visible { outline: 3px solid var(--focus); outline-offset: -3px; }

.btn--quiet {
  background: transparent;
  color: var(--ink);
  border-color: var(--rule-strong);
  font-size: var(--fs-xs);
  padding: .5rem 1rem;
}

.linkbtn {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--accent-ink);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
.linkbtn:hover { color: var(--ink); }

.filters__empty {
  margin: 0 0 var(--s-5);
  padding: var(--s-4);
  border: 1px dashed var(--rule-strong);
  font-size: var(--fs-sm);
  color: var(--body);
}
.filters__empty[hidden] { display: none; }

/* ------------------------------------------------------------- the key */

.keyrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2) var(--s-4);
  margin-bottom: var(--s-5);
}
.key {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: var(--fs-xs);
  color: var(--body);
}
.key li { display: inline-flex; align-items: center; gap: var(--s-2); }
.key__sw {
  width: 1.15rem;
  height: .7rem;
  border: 1px solid var(--rule);
  flex: none;
}
.key li[data-group="1"] .key__sw { background: var(--g1); }
.key li[data-group="2"] .key__sw { background: var(--g2); }
.key li[data-group="3"] .key__sw { background: var(--g3); }
.key li[data-group="4"] .key__sw { background: var(--g4); }
.key li[data-group="5"] .key__sw { background: var(--g5); }
.key li[data-group="6"] .key__sw { background: var(--g6); }

/* The attribute has to be re-asserted: [hidden] is display:none in the UA sheet
   and any author display rule beats it, which would leave this promising a line
   that only the script draws. */
.sched__legend[hidden] { display: none; }
/* ------------------------------------------------- the scrolling edge --
 * A table that is wider than its box gives no sign of it beyond a scrollbar,
 * and on a trackpad there is often not even that. The fade at the right edge
 * says "there is more"; /js/schedule.js measures each box and adds can-scroll
 * while there is somewhere still to go, so the fade disappears at the end
 * instead of lying. It cannot be a background on the scroller itself: the
 * tracks and the sticky column paint over it.
 *
 * Right edge only. On the left in the month view that space belongs to the
 * pinned work column, and fading it would be fading the thing that is holding
 * still. */
.scrollbox { position: relative; }
.scrollbox::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 3rem;
  background: linear-gradient(to left, var(--paper), transparent);
  opacity: 0;
  pointer-events: none;
  transition: opacity .18s ease-out;
}
.scrollbox.can-scroll::after { opacity: 1; }

/* Shown whenever a table overflows, and always in the month view, which is
   wider than any screen by design and is the same with the script off. */
.sched__hint { display: none; }
.sched-scope[data-view="months"] .sched__hint,
.sched-scope.has-overflow .sched__hint {
  display: flex;
  align-items: flex-start;
  gap: var(--s-2);
  margin: 0 0 var(--s-4);
  font-size: var(--fs-xs);
  line-height: 1.5;
  color: var(--muted);
}
.sched__hint .ico { width: 1rem; height: 1rem; fill: var(--muted); flex: none; margin-top: .15em; }

.sched__legend {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--muted);
}
.sched__legend-mark {
  display: inline-block;
  width: 2px;
  height: .9rem;
  background: var(--ink);
  flex: none;
}

/* ------------------------------------------------------- forced colours */

/* Windows High Contrast and friends paint over every background, so the six
   group colours collapse into one. The bars stay visible as marks and the
   boiler row keeps its hatch as an outline, but a key of six identical
   swatches would promise a distinction the screen cannot make, so it goes.
   Nothing is lost that matters: the row name, not the colour, is what
   identifies an item, and the dates are words in their own cells. */
@media (forced-colors: active) {
  .sched__bar { background: CanvasText; }
  .sched__row[data-group] .sched__bar[data-flag="boilers"] {
    background-image: none;
    outline: 2px dashed Canvas;
    outline-offset: -3px;
  }
  .sched__now,
  .sched__legend-mark { background: Highlight; }
  .key { display: none; }
  .sched__dot { display: none; }
}

/* --------------------------------------------------------- pulled quote */

.quote {
  margin: 0;
  padding-left: var(--s-3);
  border-left: 3px solid var(--rule-strong);
}
.quote p { font-size: var(--fs-sm); line-height: 1.5; color: var(--body); }
.quote footer { font-size: var(--fs-xs); color: var(--muted); }

/* Bar placement. One rule per column index and per span, because the
   Content-Security-Policy forbids inline style attributes. 34 months:
   March 2026 through December 2028. */
[data-from="0"] { --a: 0; }
[data-from="1"] { --a: 1; }
[data-from="2"] { --a: 2; }
[data-from="3"] { --a: 3; }
[data-from="4"] { --a: 4; }
[data-from="5"] { --a: 5; }
[data-from="6"] { --a: 6; }
[data-from="7"] { --a: 7; }
[data-from="8"] { --a: 8; }
[data-from="9"] { --a: 9; }
[data-from="10"] { --a: 10; }
[data-from="11"] { --a: 11; }
[data-from="12"] { --a: 12; }
[data-from="13"] { --a: 13; }
[data-from="14"] { --a: 14; }
[data-from="15"] { --a: 15; }
[data-from="16"] { --a: 16; }
[data-from="17"] { --a: 17; }
[data-from="18"] { --a: 18; }
[data-from="19"] { --a: 19; }
[data-from="20"] { --a: 20; }
[data-from="21"] { --a: 21; }
[data-from="22"] { --a: 22; }
[data-from="23"] { --a: 23; }
[data-from="24"] { --a: 24; }
[data-from="25"] { --a: 25; }
[data-from="26"] { --a: 26; }
[data-from="27"] { --a: 27; }
[data-from="28"] { --a: 28; }
[data-from="29"] { --a: 29; }
[data-from="30"] { --a: 30; }
[data-from="31"] { --a: 31; }
[data-from="32"] { --a: 32; }
[data-from="33"] { --a: 33; }

[data-span="1"] { --b: 1; }
[data-span="2"] { --b: 2; }
[data-span="3"] { --b: 3; }
[data-span="4"] { --b: 4; }
[data-span="5"] { --b: 5; }
[data-span="6"] { --b: 6; }
[data-span="7"] { --b: 7; }
[data-span="8"] { --b: 8; }
[data-span="9"] { --b: 9; }
[data-span="10"] { --b: 10; }
[data-span="11"] { --b: 11; }
[data-span="12"] { --b: 12; }
[data-span="13"] { --b: 13; }
[data-span="14"] { --b: 14; }
[data-span="15"] { --b: 15; }
[data-span="16"] { --b: 16; }
[data-span="17"] { --b: 17; }
[data-span="18"] { --b: 18; }
[data-span="19"] { --b: 19; }
[data-span="20"] { --b: 20; }
[data-span="21"] { --b: 21; }
[data-span="22"] { --b: 22; }
[data-span="23"] { --b: 23; }
[data-span="24"] { --b: 24; }
[data-span="25"] { --b: 25; }
[data-span="26"] { --b: 26; }
[data-span="27"] { --b: 27; }
[data-span="28"] { --b: 28; }
[data-span="29"] { --b: 29; }
[data-span="30"] { --b: 30; }
[data-span="31"] { --b: 31; }
[data-span="32"] { --b: 32; }
[data-span="33"] { --b: 33; }
[data-span="34"] { --b: 34; }
