/* =====================================================================
   Jennifer Loves Dogs — design system
   =====================================================================

   The dominant style is Bootstrap's `btn-outline-light` on the purple
   background: white 1px outline, transparent fill, white text. Every
   surface in the site conforms to it.

   The whole system is driven through Bootstrap 5.3's CSS custom
   properties, so components are retinted at the variable level rather
   than overridden selector by selector. Changing --brand-bg below
   recolours the entire site.
   ===================================================================== */

/* ---------------------------------------------------------------------
   1. Tokens
   ------------------------------------------------------------------ */

:root {
  --brand-bg:            #514092;
  --brand-bg-deep:       #3f3173;   /* recessed surfaces */
  --brand-ink:           #ffffff;

  /* Outline weights. Naming is by intent, not by value, so that
     "which white do I use here" is never a judgement call. */
  --brand-line:          rgba(255, 255, 255, 0.75);  /* primary borders */
  --brand-line-soft:     rgba(255, 255, 255, 0.28);  /* dividers */
  --brand-line-faint:    rgba(255, 255, 255, 0.14);  /* hairlines */

  --brand-fill-hover:    rgba(255, 255, 255, 0.12);
  --brand-fill-active:   rgba(255, 255, 255, 0.20);
  --brand-fill-sunken:   rgba(0, 0, 0, 0.14);

  --brand-muted:         rgba(255, 255, 255, 0.68);

  /* Availability states. Chosen to clear 4.5:1 against --brand-bg;
     never rely on colour alone — the calendar also carries text. */
  --state-open:          #7ee0a8;
  /* The "right on the boundary" amber. Reads as caution against the purple
     without competing with the green, and js/app.js hands the same value to
     the map so the pin and the sentence never disagree. */
  --state-edge:          #ffd479;
  --state-booked:        #ffb4a2;
  --state-closed:        rgba(255, 255, 255, 0.30);
  --state-read:          #7ee0a8;   /* "turns green after reading" */

  --brand-focus:         rgba(255, 255, 255, 0.55);
  --brand-radius:        0.5rem;

  --font-display: 'Pacifico', ui-rounded, 'Segoe UI', cursive;
  --font-body:    'Roboto', system-ui, -apple-system, 'Segoe UI', sans-serif;
}

/* ---------------------------------------------------------------------
   2. Bootstrap global overrides
   ------------------------------------------------------------------ */

:root,
[data-bs-theme="jld"] {
  --bs-body-bg:          var(--brand-bg);
  --bs-body-color:       var(--brand-ink);
  --bs-body-font-family: var(--font-body);
  --bs-body-font-weight: 300;
  --bs-emphasis-color:   var(--brand-ink);
  --bs-border-color:     var(--brand-line-soft);
  --bs-link-color:       var(--brand-ink);
  --bs-link-hover-color: var(--brand-ink);
  --bs-border-radius:    var(--brand-radius);

  /* Bootstrap builds focus rings from these. Setting them here means
     every focusable control gets a white ring without further work. */
  --bs-focus-ring-color: var(--brand-focus);
  --bs-focus-ring-width: 0.2rem;
}

html, body {
  background-color: var(--brand-bg);
  color: var(--brand-ink);
  font-family: var(--font-body);
  font-weight: 300;
  -webkit-font-smoothing: antialiased;
}

body { padding-bottom: 4rem; }

::selection { background: rgba(255, 255, 255, 0.25); }

/* ---------------------------------------------------------------------
   3. Display type
   ------------------------------------------------------------------ */

.partytext {
  font-family: var(--font-display);
  /* The original 180px overflowed every phone. clamp() scales with the
     viewport; the 7rem cap is what keeps the title on one line at
     desktop widths rather than wrapping to two. */
  font-size: clamp(2.25rem, 8vw, 7rem);
  font-weight: 400;
  line-height: 1.2;
  /* Pacifico has deep descenders that collide with the next element
     unless the line box is given room below the baseline. */
  padding-bottom: 0.18em;
  margin-bottom: 0.5rem;
}

.lede {
  font-size: clamp(1rem, 2.2vw, 1.2rem);
  color: var(--brand-muted);
}

/* The one line under the title. Full brand ink rather than --brand-muted:
   it is the only statement of what Jennifer actually does, so it is
   content, not a caption. */
.tagline {
  font-size: clamp(1.05rem, 2.6vw, 1.6rem);
  font-weight: 300;
  color: var(--brand-ink);
}

/* The whole page is one column of accordion. Bootstrap's .container is
   1320px at xxl, which stretches a four-row accordion into a band wider
   than it is tall and leaves the calendar stranded in the middle of it. */
.page-narrow { max-width: 60rem; }

/* ---------------------------------------------------------------------
   4. Buttons — the dominant style
   ------------------------------------------------------------------ */

.btn-outline-light {
  --bs-btn-color:              var(--brand-ink);
  --bs-btn-border-color:       var(--brand-line);
  --bs-btn-hover-color:        var(--brand-ink);
  --bs-btn-hover-bg:           var(--brand-fill-hover);
  --bs-btn-hover-border-color: var(--brand-ink);
  --bs-btn-active-color:       var(--brand-ink);
  --bs-btn-active-bg:          var(--brand-fill-active);
  --bs-btn-active-border-color: var(--brand-ink);
  --bs-btn-disabled-color:     var(--brand-line-soft);
  --bs-btn-disabled-border-color: var(--brand-line-faint);
}

/* The single solid button, reserved for the one true primary action
   (Submit). Everything else outlines. Scarcity is what makes it read
   as primary at all. */
.btn-brand {
  --bs-btn-color:              var(--brand-bg);
  --bs-btn-bg:                 var(--brand-ink);
  --bs-btn-border-color:       var(--brand-ink);
  --bs-btn-hover-color:        var(--brand-bg);
  --bs-btn-hover-bg:           rgba(255, 255, 255, 0.88);
  --bs-btn-hover-border-color: var(--brand-ink);
  --bs-btn-active-color:       var(--brand-bg);
  --bs-btn-active-bg:          rgba(255, 255, 255, 0.80);
  --bs-btn-disabled-color:     var(--brand-bg);
  --bs-btn-disabled-bg:        var(--brand-line-soft);
  --bs-btn-disabled-border-color: transparent;
}

/* Quiet tertiary action — Cancel, dismiss. Text only until hovered. */
.btn-quiet {
  --bs-btn-color:        var(--brand-muted);
  --bs-btn-border-color: transparent;
  --bs-btn-hover-color:  var(--brand-ink);
  --bs-btn-hover-bg:     var(--brand-fill-hover);
  --bs-btn-hover-border-color: transparent;
  --bs-btn-active-color: var(--brand-ink);
  --bs-btn-active-bg:    var(--brand-fill-active);
}

/* ---------------------------------------------------------------------
   5. Cards
   ------------------------------------------------------------------ */

.card {
  --bs-card-bg:          transparent;
  --bs-card-color:       var(--brand-ink);
  --bs-card-border-color: var(--brand-line);
  --bs-card-border-radius: var(--brand-radius);
  --bs-card-cap-bg:      var(--brand-fill-hover);
  --bs-card-cap-color:   var(--brand-ink);
}

/* ---------------------------------------------------------------------
   6. Accordion
   ------------------------------------------------------------------ */

.accordion {
  --bs-accordion-bg:              transparent;
  --bs-accordion-color:           var(--brand-ink);
  --bs-accordion-border-color:    var(--brand-line);
  --bs-accordion-border-radius:   var(--brand-radius);
  --bs-accordion-btn-bg:          transparent;
  --bs-accordion-btn-color:       var(--brand-ink);
  --bs-accordion-active-bg:       var(--brand-fill-hover);
  --bs-accordion-active-color:    var(--brand-ink);
  --bs-accordion-btn-focus-box-shadow: 0 0 0 0.2rem var(--brand-focus);
}

/* Bootstrap bakes the chevron colour into an SVG data-URI, so it cannot
   be recoloured by a variable. These are the same paths with a white
   stroke. */
.accordion {
  --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
  --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}

.accordion-button { font-weight: 400; }

/* The read-receipt and locked-section styles were removed 2026-08-26 with
   the gating sequence itself. They are in docs/attic/site.css.phase2, and
   harness.html still exercises .is-read / .is-locked — that page was
   already stale (see CLAUDE.md) and those two rows are now dead in it. */

/* The dog photos, inside the About cell (private/lib/media.php).

   ONE FRAME, FIXED, AND THE PICTURE FITS INSIDE IT. Twenty-six of the
   twenty-nine photos are portrait and three are landscape, in three
   different aspect ratios. Two consequences drove every rule below:

     - A frame that resizes per slide makes the accordion cell jump height
       on every click, which moves the paragraph the visitor is reading.
       So the height is fixed and identical for all slides, which also
       means no layout shift while the images load — the space is already
       reserved, so no width/height attributes are needed on the img.
     - object-fit: contain, never cover. Cover would fill the frame
       edge-to-edge and crop a 9:16 phone photo hard enough to take the
       dog's head off; these are pictures of specific animals, so losing
       part of one to tidiness is the wrong trade. The cost is purple bars
       beside the tall ones, which is why the frame is capped at 32rem
       rather than running the full 60rem column — at full width the bars
       are wider than the photograph.

   min(75vh, …) is the short-window case: on a phone held sideways a flat
   34rem frame is taller than the screen, and the controls end up off it. */
.dog-carousel {
  max-width: 32rem;
  margin: 1.5rem auto 0;
  background: var(--brand-fill-sunken);
  border: 1px solid var(--brand-line-soft);
  border-radius: var(--brand-radius);
  overflow: hidden;
}

.dog-carousel .carousel-item { height: min(75vh, 34rem); }

.dog-carousel .carousel-item img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Bootstrap's controls are white SVGs at 50% opacity over a transparent
   strip. That is legible over a dark photo and invisible over a bright
   one — half these pictures are a pale sofa in daylight. The disc gives
   them a background of their own so they cannot disappear into the
   image. 2.75rem is 44px: the minimum comfortable touch target. */
.dog-carousel .carousel-control-prev,
.dog-carousel .carousel-control-next {
  width: 20%;
  opacity: 1;
}

.dog-carousel .carousel-control-prev-icon,
.dog-carousel .carousel-control-next-icon {
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  background-color: rgba(0, 0, 0, 0.45);
  background-size: 1.15rem 1.15rem;
  background-position: center;
  background-repeat: no-repeat;
}

.dog-carousel .carousel-control-prev:hover .carousel-control-prev-icon,
.dog-carousel .carousel-control-next:hover .carousel-control-next-icon {
  background-color: rgba(0, 0, 0, 0.7);
}

/* Keyboard users move between these two buttons and nothing else, so the
   focus ring is the only thing telling them where they are. */
.dog-carousel .carousel-control-prev:focus-visible .carousel-control-prev-icon,
.dog-carousel .carousel-control-next:focus-visible .carousel-control-next-icon {
  outline: 2px solid var(--brand-ink);
  outline-offset: 2px;
}

/* "4 / 29" instead of 29 indicator dots, which at this count is a grey
   smear no one can aim at. aria-hidden in the markup: each slide already
   announces its own position, and this would say it a second time. */
.dog-carousel .carousel-counter {
  position: absolute;
  right: 0.5rem;
  bottom: 0.5rem;
  margin: 0;
  padding: 0.1rem 0.55rem;
  border-radius: 10rem;
  background: rgba(0, 0, 0, 0.45);
  color: var(--brand-ink);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
}

/* No prefers-reduced-motion block here on purpose: Bootstrap's own
   stylesheet already sets .carousel-item { transition: none } under that
   query, and the carousel never advances by itself, so there is no motion
   left to suppress. A second rule saying the same thing is a second rule
   to keep in step. */

/* The mid-day visit checker, inside the services cell. Ruled off from the
   prose above it, because it is a tool and the rest of the cell is
   reading — without the line the address field looks like part of the
   paragraph. */
.zone-tool {
  border-top: 1px solid var(--brand-line-faint);
  padding-top: 1.5rem;
}

/* The calendar cell. FullCalendar is happiest with room; capping the width
   here rather than on #calendar keeps the key and the stale notice aligned
   with the grid instead of with the panel. */
.calendar-wrap { max-width: 40rem; margin-inline: auto; }

/* ---------------------------------------------------------------------
   7. Forms
   ------------------------------------------------------------------ */

.form-control,
.form-select,
.input-group-text,
.form-check-input {
  background-color: var(--brand-fill-sunken);
  border-color: var(--brand-line-soft);
  color: var(--brand-ink);
}

.form-control::placeholder { color: rgba(255, 255, 255, 0.45); }

/* Bootstrap's select chevron is #343a40 — effectively invisible here. */
.form-select {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}

.form-control:focus,
.form-select:focus {
  background-color: var(--brand-fill-sunken);
  border-color: var(--brand-ink);
  color: var(--brand-ink);
  box-shadow: 0 0 0 0.2rem var(--brand-focus);
}

.input-group-text {
  min-width: 5.5rem;
  background-color: var(--brand-fill-hover);
  border-color: var(--brand-line-soft);
}

.form-check-input:checked {
  background-color: var(--brand-ink);
  border-color: var(--brand-ink);
}
/* Bootstrap's tick glyph is white, which vanishes against the white fill
   above. Repaint it in the background purple. */
.form-check-input:checked[type="checkbox"] {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23514092' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e");
}
.form-switch .form-check-input:checked {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23514092'/%3e%3c/svg%3e");
}
.form-check-input:focus { box-shadow: 0 0 0 0.2rem var(--brand-focus); }

.form-label { color: var(--brand-muted); }

/* Native date pickers render a dark calendar glyph that vanishes on
   purple. Invert it back to white. */
.form-control[type="date"]::-webkit-calendar-picker-indicator,
.form-control[type="time"]::-webkit-calendar-picker-indicator {
  filter: invert(1);
  opacity: 0.75;
  cursor: pointer;
}

/* Native number spinners render as a dark block on the sunken fill and
   invert badly. Hide them — these are small counts and typing is fine.
   A custom stepper is a later nicety, not a Phase 0 problem. */
.form-control[type="number"]::-webkit-inner-spin-button,
.form-control[type="number"]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.form-control[type="number"] { appearance: textfield; -moz-appearance: textfield; }

/* ---------------------------------------------------------------------
   8. Modals
   ------------------------------------------------------------------ */

.modal {
  --bs-modal-bg:           var(--brand-bg);
  --bs-modal-color:        var(--brand-ink);
  --bs-modal-border-color: var(--brand-line);
  --bs-modal-border-radius: var(--brand-radius);
  --bs-modal-header-border-color: var(--brand-line-soft);
  --bs-modal-footer-border-color: var(--brand-line-soft);
}

.modal-backdrop { --bs-backdrop-bg: #1c1436; --bs-backdrop-opacity: 0.75; }

/* The close glyph is a dark SVG by default. */
.btn-close { filter: invert(1) grayscale(100%); opacity: 0.8; }
.btn-close:focus { box-shadow: 0 0 0 0.2rem var(--brand-focus); opacity: 1; }

/* ---------------------------------------------------------------------
   9. Alerts, badges, misc
   ------------------------------------------------------------------ */

.alert {
  --bs-alert-bg:           var(--brand-fill-hover);
  --bs-alert-border-color: var(--brand-line);
  --bs-alert-color:        var(--brand-ink);
}

.list-group {
  --bs-list-group-bg:           transparent;
  --bs-list-group-color:        var(--brand-ink);
  --bs-list-group-border-color: var(--brand-line-soft);
  --bs-list-group-action-hover-bg: var(--brand-fill-hover);
  --bs-list-group-action-hover-color: var(--brand-ink);
}

code {
  color: #ffd9a0;
  background: var(--brand-fill-sunken);
  padding: 0.1em 0.35em;
  border-radius: 0.25rem;
}

hr { border-color: var(--brand-line-soft); opacity: 1; }

/* ---------------------------------------------------------------------
   10. Availability calendar (FullCalendar)
   ------------------------------------------------------------------ */

.fc {
  --fc-border-color:           var(--brand-line-soft);
  --fc-page-bg-color:          transparent;
  --fc-neutral-bg-color:       var(--brand-fill-hover);
  --fc-today-bg-color:         var(--brand-fill-hover);
  --fc-button-text-color:      var(--brand-ink);
  --fc-button-bg-color:        transparent;
  --fc-button-border-color:    var(--brand-line);
  --fc-button-hover-bg-color:  var(--brand-fill-hover);
  --fc-button-hover-border-color: var(--brand-ink);
  --fc-button-active-bg-color: var(--brand-fill-active);
  --fc-button-active-border-color: var(--brand-ink);
  color: var(--brand-ink);
}

.fc .fc-toolbar-title { font-size: 1.1rem; font-weight: 400; }
.fc .fc-col-header-cell-cushion,
.fc .fc-daygrid-day-number { color: var(--brand-ink); text-decoration: none; }

/* The three availability states. Each carries a text label as well as a
   fill, so the meaning does not depend on colour perception. */
/* Fills carry an inset edge as well as a tint: at 0.16 alpha on purple
   the three states were nearly indistinguishable, and colour alone is
   not a sufficient signal anyway. */
.fc .fc-daygrid-day.day-open {
  background: rgba(126, 224, 168, 0.22);
  box-shadow: inset 3px 0 0 var(--state-open);
}
.fc .fc-daygrid-day.day-booked {
  background: rgba(255, 180, 162, 0.22);
  box-shadow: inset 3px 0 0 var(--state-booked);
}
.fc .fc-daygrid-day.day-closed {
  background: rgba(255, 255, 255, 0.06);
  box-shadow: inset 3px 0 0 var(--state-closed);
}
.fc .fc-daygrid-day.day-closed .fc-daygrid-day-number,
.fc .fc-daygrid-day.day-unavailable .fc-daygrid-day-number {
  color: var(--brand-line-soft);
  text-decoration: line-through;
}
/* PUBLIC calendar only: a booked day is filled SOLID in the same colour as
   the "Booked" swatch in the key beneath it (2026-09-25). The tint + inset
   edge above made booked days read as the old "Closed" swatch, and visitors
   could not tell booked from not. The key now carries only "Booked", so the
   cell has to look like that one swatch — anything unfilled is simply free.
   Scoped to .calendar-wrap on purpose: the admin calendar (.jld-cal) reuses
   .day-booked as a thin left rule so it never competes with its SOLID
   request bars — see the Admin calendar section. The day number goes dark
   because white on #ffb4a2 is unreadable; --brand-bg-deep on it is ~6.8:1. */
.calendar-wrap .fc .fc-daygrid-day.day-booked {
  background: var(--state-booked);
  box-shadow: none;
}
.calendar-wrap .fc .fc-daygrid-day.day-booked .fc-daygrid-day-number {
  color: var(--brand-bg-deep);
  font-weight: 500;
}
/* Spill-over days from the next/previous month stay booked-coloured but
   quieter, matching how FullCalendar already fades their numbers. */
.calendar-wrap .fc .fc-daygrid-day.fc-day-other.day-booked {
  background: rgba(255, 180, 162, 0.45);
}

.fc .fc-daygrid-day.day-selected { outline: 2px solid var(--brand-ink); outline-offset: -2px; }

.availability-key { display: flex; gap: 1rem; flex-wrap: wrap; font-size: 0.85rem; color: var(--brand-muted); }
.availability-key .swatch { display: inline-block; width: 0.85rem; height: 0.85rem; border-radius: 0.2rem; margin-right: 0.35rem; vertical-align: -1px; }
.availability-key .swatch.open   { background: var(--state-open); }
.availability-key .swatch.booked { background: var(--state-booked); }
.availability-key .swatch.closed { background: var(--state-closed); }

/* ---------------------------------------------------------------------
   11. The map

   Sections 11 (page states) and 12 (the request modal's stage-aware
   context panel) were removed 2026-08-26 along with the markup they
   styled — the [data-when] visibility system and .context-panel had no
   selectors left on any deployed page. Both are in
   docs/attic/site.css.phase2. harness.html still contains [data-when]
   swatches, which no longer do anything; it was already stale.
   ------------------------------------------------------------------ */

.map-frame {
  position: relative;
  width: 100%;
  padding-bottom: 75%;
  border: 1px solid var(--brand-line-soft);
  border-radius: var(--brand-radius);
  overflow: hidden;
}
.map-frame > * { position: absolute; inset: 0; }

/* Covers the map until someone asks for it. A Dynamic Map load is billed
   per load, so it waits for a real click rather than firing whenever focus
   happens to land in the address field. */
.map-gate {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: var(--brand-fill-sunken);
}
.map-gate[hidden] { display: none; }

/* Zone verdict, shown once an address geocodes. */
/* Admin. Not deployed to visitors, but it uses the same tokens so the two
   surfaces stay one design rather than drifting into two. */
.admin-bar {
  background: var(--brand-bg-deep);
  border-bottom: 1px solid var(--brand-line-soft);
}

.status-pill {
  display: inline-block;
  padding: 0.1rem 0.6rem;
  border-radius: 10rem;
  border: 1px solid currentColor;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.status-pending   { color: var(--state-edge); }
.status-confirmed { color: var(--state-open); }
.status-completed { color: var(--brand-muted); }
.status-declined,
.status-cancelled { color: var(--state-booked); }

.zone-verdict { font-size: 0.9rem; }
.zone-verdict.in-zone   { color: var(--state-open); }
.zone-verdict.edge-zone { color: var(--state-edge); }
.zone-verdict.out-zone  { color: var(--state-booked); }

/* Why a typed date range was refused. Same red as a booked day, because
   it is usually a booked day that caused it. */
.date-problem {
  font-size: 0.9rem;
  color: var(--state-booked);
}
.date-problem[hidden] { display: none; }

/* Field-level validation, in the site's own palette rather than
   Bootstrap's. Bootstrap paints an SVG warning glyph into the control's
   background on .is-invalid, which collides with the input-group styling
   here and reads as a rendering fault — so it is switched off and the
   message below the field does the work. */
.field-error {
  font-size: 0.85rem;
  color: var(--state-booked);
  margin: 0.35rem 0 0;
}
.field-error[hidden] { display: none; }

.form-control.is-invalid,
.form-control.is-invalid:focus {
  border-color: var(--state-booked);
  background-image: none;
  padding-right: 0.75rem;
}
.form-control.is-invalid:focus {
  box-shadow: 0 0 0 var(--bs-focus-ring-width) rgba(255, 180, 162, 0.35);
}

/* On phones the context panel stacks below the form rather than beside
   it, and the month grid gets its own breathing room. */
@media (max-width: 767.98px) {
  .context-panel { margin-top: 1.5rem; }
  .fc .fc-toolbar { flex-direction: column; gap: 0.5rem; }
  .input-group-text { min-width: 4.5rem; font-size: 0.9rem; }
}

/* ---------------------------------------------------------------------
   13. Accessibility
   ------------------------------------------------------------------ */

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--brand-ink);
  color: var(--brand-bg);
  padding: 0.6rem 1rem;
  z-index: 2000;
}
.skip-link:focus { left: 0; }

:focus-visible { outline: 2px solid var(--brand-ink); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ---------------------------------------------------------------------
   Admin calendar (admin/_calendar.php, js/admin-calendar.js)

   Two layers that must stay visually distinct, because their disagreeing
   is the whole point of the screen:

     day shading  — what Google Calendar says (reuses .day-booked etc.)
     request bars — what Jennifer agreed to

   So the bars are SOLID BLOCKS and the shading is a left inset rule. If
   they ever read as one thing, a confirmed booking sitting on an unshaded
   day stops being obvious, which is the one signal this screen exists for.
   --------------------------------------------------------------------- */

.jld-cal { --fc-border-color: var(--brand-line-faint); }

/* Sticky only from Bootstrap's lg breakpoint, where the two columns exist.

   Below it the calendar is stacked ABOVE the queue, and a sticky element
   there is at best inert (its containing block is no taller than itself) and
   at worst pins a month grid over the cards you are trying to read. Measured
   at 500px before this rule existed: position resolved to `sticky`, which is
   not what "sticky on desktop" was supposed to mean. */
@media (min-width: 992px) {
  .jld-cal-sticky { position: sticky; top: 1rem; }
}

/* Request bars. Pending and confirmed use the same two colours as the
   status pills on the queue (.status-pending / .status-confirmed), defined
   once so the two screens cannot drift. */
.jld-cal .fc-daygrid-event.jld-req {
  border: none;
  padding: 0.1rem 0.35rem;
  font-size: 0.8rem;
  color: var(--brand-bg);
  background: var(--brand-line);
  cursor: pointer;
}

.jld-cal .fc-daygrid-event.jld-req-pending   { background: var(--state-edge); }
.jld-cal .fc-daygrid-event.jld-req-confirmed { background: var(--state-open); }

/* Confirmed, but the days are not blocked on Google Calendar — so the site
   is still selling them.

   FIRST ATTEMPT WAS A FULL 45° HATCH BEHIND THE LABEL, and it was rejected
   on sight in a screenshot: the client's name was illegible on top of it.
   A warning nobody can read the name in is not a warning. So the fill stays
   solid and the signal moves to the border and the label. */
.jld-cal .fc-daygrid-event.jld-req-unsynced {
  background: var(--state-booked);
  color: var(--brand-bg);
  border: 2px dashed var(--brand-bg-deep);
  font-weight: 500;
}

/* Not colour alone. js/admin-calendar.js prefixes the title with "!" so the
   state survives a greyscale print, a colour-blind reader, and the legend
   being scrolled off — the same reason the zone verdict has three named
   states rather than a red/green pair. */
.jld-cal .fc-daygrid-event.jld-req-unsynced .fc-event-title::before {
  content: '! ';
  font-weight: 700;
}

.jld-cal-legend { font-size: 0.85rem; color: var(--brand-muted); }

.jld-key {
  display: inline-block;
  width: 0.9rem;
  height: 0.9rem;
  vertical-align: -0.1rem;
  border-radius: 0.15rem;
  margin-right: 0.15rem;
}
.jld-key-pending   { background: var(--state-edge); }
.jld-key-confirmed { background: var(--state-open); }
.jld-key-booked    { background: transparent; box-shadow: inset 3px 0 0 var(--state-booked); border: 1px solid var(--brand-line-faint); }
.jld-key-unsynced  { background: var(--state-booked); border: 2px dashed var(--brand-bg-deep); }

/* Selection (js/admin-calendar.js). Presentation only — it never changes
   which requests are listed. */

/* The selected request's days. .day-selected already existed in this file,
   written for the public calendar and unused by it; reused rather than
   inventing a second highlight. */

.jld-cal .fc-daygrid-event.jld-req-selected {
  outline: 2px solid var(--brand-ink);
  outline-offset: 1px;
}

.card.is-selected {
  border-color: var(--brand-line);
  box-shadow: 0 0 0 2px var(--brand-line);
}

/* The cards are clickable, so say so — but only where a click actually
   selects. Anything interactive inside keeps its own cursor, because the
   click handler deliberately lets those win. */
.card[data-request-id] { cursor: pointer; }
.card[data-request-id] a,
.card[data-request-id] button,
.card[data-request-id] input,
.card[data-request-id] label { cursor: revert; }
