/*
 * Application styling on top of Beer CSS (Material Design 3).
 *
 * Beer CSS supplies the design system - colour roles, elevation, typography, and the component set
 * including the app bar, navigation rail/bar and dialogs. This file has two jobs: layout glue Beer
 * doesn't provide, and a deliberate density/motion pass on top of Beer's defaults - which are tuned
 * for a spacious, static admin console, not the tighter, more responsive feel of a modern app.
 *
 * The single biggest lever here is `.field`'s default 2rem bottom margin: every form on every page
 * inherits it, so tightening it once here reads as "the whole app got more compact" without editing
 * forty templates individually. Same logic applies to the hover/elevation and heading-size rules
 * below - they target Beer's own base selectors so every page that already uses `article`, `.field`,
 * or a card heading picks up the change automatically.
 */

:root {
  /* A slightly quicker default than Beer's --speed2 (.2s) - snappier without feeling abrupt. */
  --app-speed: 0.15s;
}

/* --- color themes ---------------------------------------------------------------------------- */

/*
 * Beer CSS's whole design system is built on these custom properties - every component references
 * them rather than a hard-coded color, which is what makes swapping the full palette this cheap.
 * `light` and `dark` are Beer's own built-in themes (see beer.min.css `:root, body.light` and
 * `body.dark`); only the two high-contrast themes below are new.
 *
 * Both aim past WCAG AA at AAA-level contrast (~7:1 for normal text) by using pure black/white for
 * text and surfaces rather than tinted grays, and by not relying on color alone to carry meaning -
 * outlines stay a full-strength black/white too, not a softened "outline-variant" gray, so borders
 * and focus rings stay visible without depending on hue discrimination.
 */
body.hc-light {
  --primary: #000000;
  --on-primary: #ffffff;
  --primary-container: #000000;
  --on-primary-container: #ffffff;
  --secondary: #000000;
  --on-secondary: #ffffff;
  --secondary-container: #ffffff;
  --on-secondary-container: #000000;
  --tertiary: #000000;
  --on-tertiary: #ffffff;
  --tertiary-container: #ffffff;
  --on-tertiary-container: #000000;
  /* Dark enough on its own that white text on it still clears AAA. */
  --error: #7a0000;
  --on-error: #ffffff;
  --error-container: #7a0000;
  --on-error-container: #ffffff;
  --background: #ffffff;
  --on-background: #000000;
  --surface: #ffffff;
  --on-surface: #000000;
  --surface-variant: #ffffff;
  --on-surface-variant: #000000;
  --outline: #000000;
  --outline-variant: #000000;
  --inverse-surface: #000000;
  --inverse-on-surface: #ffffff;
  --inverse-primary: #ffffff;
  --surface-dim: #ffffff;
  --surface-bright: #ffffff;
  --surface-container-lowest: #ffffff;
  --surface-container-low: #ffffff;
  --surface-container: #ffffff;
  --surface-container-high: #f0f0f0;
  --surface-container-highest: #e0e0e0;
}

body.hc-dark {
  /* Bright yellow-on-black is the conventional "this is high contrast mode" signal (shared with
     OS-level high-contrast themes), so it doubles as a recognizable cue, not just a color choice. */
  --primary: #ffd400;
  --on-primary: #000000;
  --primary-container: #ffd400;
  --on-primary-container: #000000;
  --secondary: #ffffff;
  --on-secondary: #000000;
  --secondary-container: #ffffff;
  --on-secondary-container: #000000;
  --tertiary: #ffffff;
  --on-tertiary: #000000;
  --tertiary-container: #ffffff;
  --on-tertiary-container: #000000;
  --error: #ff6e6e;
  --on-error: #000000;
  --error-container: #ff6e6e;
  --on-error-container: #000000;
  --background: #000000;
  --on-background: #ffffff;
  --surface: #000000;
  --on-surface: #ffffff;
  --surface-variant: #000000;
  --on-surface-variant: #ffffff;
  --outline: #ffffff;
  --outline-variant: #ffffff;
  --inverse-surface: #ffffff;
  --inverse-on-surface: #000000;
  --inverse-primary: #000000;
  --surface-dim: #000000;
  --surface-bright: #000000;
  --surface-container-lowest: #000000;
  --surface-container-low: #000000;
  --surface-container: #000000;
  --surface-container-high: #1a1a1a;
  --surface-container-highest: #262626;
}

/* Both high-contrast themes: thicker, unambiguous borders and a bolder focus ring, since a subtle
   1px hairline is exactly what this theme exists to avoid. */
body.hc-light .border,
body.hc-dark .border,
body.hc-light table.border,
body.hc-dark table.border {
  border-width: 0.125rem;
}

body.hc-light :is(a, button, .button, .chip):focus-visible,
body.hc-dark :is(a, button, .button, .chip):focus-visible {
  outline-width: 0.1875rem;
}

/* --- hidden attribute wins over layout classes ------------------------------------------------ */

/* Beer's `.row`/`.grid` classes set `display: flex`/`grid` directly, which (being author CSS) beats
   the `[hidden] { display: none }` the browser's UA stylesheet provides - so any element that is
   both `hidden` and one of those layout classes stays visible. Every vanilla-JS show/hide toggle in
   this app (recurrence controls, cascading location/room pickers, bulk-hours panels, etc.) relies on
   `hidden` actually hiding the element, so this override has to be global rather than fixed up one
   template at a time. */
[hidden] {
  display: none !important;
}

/* --- printing --------------------------------------------------------------------------------- */

/* The app's own chrome (top/bottom nav, filter toolbars, action buttons) has no place on a printed
   page - a page that wants to print cleanly (e.g. the resident schedule) marks the parts that
   shouldn't print with `.no-print`, on top of the header/nav this always hides. There is no
   server-rendered print/PDF view anywhere in the app; "printable" everywhere means the browser's own
   print dialog against this stylesheet. */
@media print {
  header,
  nav,
  .no-print {
    display: none !important;
  }
}

/* --- density: forms -------------------------------------------------------------------------- */

/* Beer's default is 2rem. Cut roughly in half - the biggest single contributor to how "spacious"
   (read: dated) a form-heavy admin screen feels. */
.field {
  margin-block-end: 1rem;
}

.grid > * > .field {
  margin-block-end: 0.75rem;
}

.grid > * > .field + .field {
  margin-block-start: 1rem;
}

/* --- density: cards -------------------------------------------------------------------------- */

article {
  padding: 0.875rem 1rem;
}

/* Card headings (the `<h6>` at the top of nearly every `article class="round"` in this app) don't
   need to carry full heading weight - they're a card label, not a page title. */
article > h6:first-child {
  font-size: clamp(1rem, 4vw, 1.125rem);
  font-weight: 600;
  margin-block-start: 0;
  margin-block-end: 0.5rem;
}

article > h6:first-child + p.small-text {
  margin-block-start: -0.25rem;
}

/* --- interactive feel ------------------------------------------------------------------------ */

/*
 * Applied to anything that is clickable as a whole unit but isn't itself a <button>/<a> - an entry
 * card, a dashboard stat tile, a list row. Beer's `article` already transitions `transform`, so this
 * just adds the hover/active states on top: a small lift on hover, a settle on press. `:has(a:hover)`
 * covers cards whose click target is technically an inner link/thumbnail rather than the card itself.
 */
.hoverable {
  cursor: pointer;
  transition: box-shadow var(--app-speed), transform var(--app-speed);
}

.hoverable:hover,
.hoverable:has(a:hover),
.hoverable:focus-within {
  box-shadow: var(--elevate2);
  transform: translateY(-0.125rem);
}

.hoverable:active {
  transform: translateY(0);
  box-shadow: var(--elevate1);
}

/* Table rows get the same subtle feedback - makes a dense admin table feel responsive rather than
   like a static report. */
.table-scroll table tbody tr {
  transition: background-color var(--app-speed);
}

.table-scroll table tbody tr:hover {
  background-color: var(--surface-container-high);
}

/* --- stat tiles (dashboards) ------------------------------------------------------------------ */

/*
 * KPI tiles on the admin/super-admin dashboards. A big number reads faster than a labelled table for
 * "how many pending requests do I have right now", which is the actual question those screens answer.
 */
.stat-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.125rem;
  padding: 1.25rem 1rem;
}

.stat-tile .stat-value {
  font-size: clamp(1.5rem, 8vw, 2.25rem);
  font-weight: 700;
  line-height: 1;
  color: var(--primary);
}

.stat-tile .stat-label {
  font-size: 0.8125rem;
  color: var(--on-surface-variant);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

/* Theme picker shown pre-login (base.html), since the authenticated header it normally lives in
   isn't rendered at all when signed out. */
.fixed-theme-picker {
  position: fixed;
  top: 0.5rem;
  right: 0.5rem;
  z-index: 12;
}

/*
 * The custom theme's color-wheel trigger.
 *
 * Beer CSS's default for every `input[type=color]` is opacity 0, absolutely positioned to fill its
 * nearest positioned ancestor - a trick meant for layering the native control, invisibly, on top of a
 * separately-styled swatch. Inside a `menu` item that's an extra moving part this control doesn't
 * need: a full-bleed invisible layer sitting a few rows above another clickable link is exactly the
 * kind of setup that can end up catching a click meant for something else. This overrides that back
 * to an ordinary, visible, normally-flowed circle instead - what a color input already looks like
 * everywhere it isn't fighting Beer's own hiding rule - so there's nothing invisible left to overlap.
 */
.theme-swatch {
  display: inline-flex;
  width: 2rem;
  height: 2rem;
  flex-shrink: 0;
}

/* The <label> wrapping .theme-swatch plus the "Custom" text - one click target for the whole row,
   since it forwards a click anywhere inside it to the color input it wraps. */
.theme-swatch-label {
  cursor: pointer;
}

.theme-swatch input[type="color"] {\
  opacity: 1;
  position: static;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  padding: 0;
  border: 0.0625rem solid var(--outline-variant);
  border-radius: 50%;
  outline: 0;
  cursor: pointer;
}

/* --- the fixed app bar overlaps page content by default ------------------------------------ */
main.has-nav {
  padding-top: 4.5rem;
  padding-bottom: calc(5rem + env(safe-area-inset-bottom, 0px));
}

@media only screen and (min-width: 601px) {\
  main.has-nav {
    /* No left-padding reservation here on wide screens - navigation moved from a fixed left rail
       into the header bar itself (see layout/base.html and friends), so there's no longer a rail
       for content to clear. */
    padding-bottom: 2rem;
  }
}

/* --- entry photos -------------------------------------------------------------------------- */

/*
 * 150x150 by default (still used as-is for the current-photo preview on the entry edit form).
 * Fixed dimensions rather than max-width so rows stay aligned even while images are still loading.
 */
.entry-thumb {
  width: 150px;
  height: 150px;
  object-fit: cover;
  border-radius: 0.75rem;
  transition: transform var(--app-speed);
}

/* A slight zoom on hover reinforces that the thumbnail opens the full-size original - purely a
   feedback cue, since `a.photo-link` below already sets the zoom-in cursor. */
a.photo-link:hover .entry-thumb {\
  transform: scale(1.03);
}

/* Directory cards (see entries/list.html) shrink the thumbnail well below the 150px default -
   at three cards per row, that size was leaving barely any width for a long name or the "Lot #"
   line beside it, which wrapped and got cut off. Scoped to .entry-card rather than a second
   fragment or a modifier param, so the shared thumb(entry) fragment stays a single source of truth
   for the markup itself. */
.entry-card .entry-thumb {
  width: 88px;
  height: 88px;
}

/* Two names ("Smith, Jane & John") run noticeably longer than one - shrink the heading a bit so a
   pair still fits the card's width instead of wrapping. See Entry.directoryHeading. */
.entry-card-heading-pair {
  font-size: 1.2rem;
}

/*
 * Beer styles a direct-child <a> as a row flexbox (icon-plus-label links elsewhere in the app), so
 * a card whose whole clickable area is one <a class="max"> wrapping several separate block
 * elements (a title, a subtitle line, ...) got those elements laid out side by side instead of
 * stacked - they'd silently overflow a narrow card, or just barely fit a wide one, rather than
 * wrapping to their own lines. .card-link restores plain block display for that content while
 * .max's flex:1 sizing still applies (it's still a flex item within the surrounding .row) - see the
 * entry directory cards and the admin approval queue, the two places this pattern is used.
 */
.card-link {
  display: block;
}

.entry-thumb-small {
  width: 56px;
  height: 56px;
  object-fit: cover;
  border-radius: 50%;
}

/* Requirement: "small in display pages (approx 400x300)". */
.entry-photo {
  max-width: 400px;
  max-height: 300px;
  width: 100%;
  height: auto;
  object-fit: contain;
  border-radius: 0.75rem;
}

/* The photo links to its full-size variant, so signal that it is clickable. */
a.photo-link {
  display: inline-block;
  cursor: zoom-in;
}

/* Positions the "In the Park" star badge against the corner of whichever photo/thumbnail/avatar it
   wraps - see fragments/image.html's inParkStar fragment and Entry.showsInParkStar. */
.entry-photo-wrap {
  position: relative;
  display: inline-block;
}

/* Fixed blue rather than the current color theme's --primary: the requirement is specifically a
   blue star, and the app's own theme picker (see fragments/theme-picker.html) lets an admin choose
   any color, including ones this badge would disappear against. */
.in-park-star {
  position: absolute;
  right: -0.25rem;
  bottom: -0.25rem;
  width: 1.35rem;
  height: 1.35rem;
  border-radius: 50%;
  background: #1976d2;
  color: #ffffff;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
  pointer-events: none;
}

.in-park-star i {
  font-size: 0.9rem;
}

/* --- lists and tables ---------------------------------------------------------------------- */

/*
 * Wide admin tables must scroll inside their own container rather than forcing the whole page to
 * scroll horizontally on a phone.
 */
.table-scroll {
  overflow-x: auto;
  width: 100%;
  max-width: 100%;
  -webkit-overflow-scrolling: touch;
  border-radius: 0.5rem;
}

.table-scroll table {
  min-width: max-content;
  width: 100%;
}

.table-scroll table th,
.table-scroll table td {
  white-space: nowrap;
}

/* --- change request diff ------------------------------------------------------------------- */

.diff-row.changed {
  background: var(--secondary-container);
}

.diff-old {
  text-decoration: line-through;
  opacity: 0.7;
}

.diff-new {
  font-weight: 600;
}

/* --- misc ---------------------------------------------------------------------------------- */

.no-image-caption {
  color: var(--on-surface-variant);
  font-style: italic;
}

/* Repeating form rows (names, emails, phones, home addresses). */
.repeat-row {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  flex-wrap: wrap;
  margin-block-end: 0.5rem;
}

.repeat-row > .field {
  flex: 1 1 12rem;
  margin-block-end: 0;
}

/* The per-row delete button (see entries/form.html) - keep it from being squeezed by its flex
   siblings, and settle it against the field baseline rather than the label above the field. */
.repeat-row > [data-remove-row] {\
  flex-shrink: 0;
  align-self: center;
}

/* Drag-to-reorder handle (see repeat-rows.js's [data-sortable] wiring) - only this icon is
   `draggable`, not the row, so grabbing to select text in a field never starts a drag. */
.repeat-row > [data-drag-handle] {
  flex-shrink: 0;
  align-self: center;
  cursor: grab;
  color: var(--outline, #79747e);
}

.repeat-row.dragging {
  opacity: 0.4;
}

/* --- page headers ---------------------------------------------------------------------------- */

/*
 * The "<h5>Title</h5> ... action button" row pattern used at the top of most list/detail screens.
 * Named so it can be reused deliberately rather than reconstructed with ad-hoc flex/nav markup on
 * every page.
 */
.page-header {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-block-end: 1rem;
}

.page-header h5,
.page-header h6 {
  margin: 0;
  flex: 1;
}

/* --- floating action button (FAB) ----------------------------------------------------------- */
.fab {
  position: fixed;
  right: 1.5rem;
  bottom: 2rem;
  z-index: 10;
  box-shadow: var(--elevate2);
  transition: transform var(--app-speed), box-shadow var(--app-speed);
}

.fab:hover {
  box-shadow: var(--elevate3);
  transform: scale(1.05);
}

.fab:active {
  box-shadow: var(--elevate1);
  transform: scale(0.95);
}

/* Placed after the rules above rather than up near main.has-nav (where it was originally written) -
   .page-header's own margin-block-end is defined further down the file than that first draft
   location, and CSS breaks ties between equal-specificity rules by source order, not by which one
   sits inside an active media query. From up there, this whole block's .page-header override was
   silently dead on every screen width. */
@media only screen and (max-width: 600px) {
  header.fixed nav {
    padding: 0 0.5rem;
  }

  header.fixed h6.max {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  main.responsive {
    padding-left: 0.75rem;
    padding-right: 0.75rem;
    max-width: 100vw;
    box-sizing: border-box;
    overflow-x: hidden;
  }

  main.has-nav {
    padding-top: 3.5rem;
    padding-bottom: calc(4.75rem + env(safe-area-inset-bottom, 0px));
  }

  nav.bottom {
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }

  /* Allow flex rows to wrap on narrow viewports unless explicitly marked .no-wrap */
  .row:not(.no-wrap) {
    flex-wrap: wrap;
    max-width: 100%;
  }

  form.row > .field {
    flex: 1 1 100%;
    min-width: 0;
  }

  .repeat-row > .field {
    flex: 1 1 100%;
    min-width: 0;
  }

  /* Page headers and sub-navigation action bars wrap instead of forcing horizontal overflow */
  .page-header {
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-block-end: 0.75rem;
  }

  .page-header h5,
  .page-header h6 {
    min-width: 0;
    flex: 1 1 auto;
  }

  nav:not(.bottom) {
    flex-wrap: wrap;
    gap: 0.5rem;
  }

  nav:not(.bottom) > h5.max,
  nav:not(.bottom) > h6.max {
    flex: 1 1 100%;
    margin-block-end: 0.25rem;
  }

  nav:not(.bottom) a.button,
  nav:not(.bottom) button {
    margin: 0;
  }

  /* Tenant switcher popup: on mobile, pop up to the right of the icon to avoid clipping on the left edge */
  .tenant-switcher menu,
  menu.tenant-menu {
    inset: auto auto 0 0 !important;
  }

  /* Keep table rows and emails horizontal and scrollable rather than breaking vertically */
  .table-scroll table {
    min-width: max-content;
    width: 100%;
  }

  .table-scroll table th,
  .table-scroll table td {
    padding: 0.625rem 0.75rem;
    font-size: 0.875rem;
    white-space: nowrap;
    word-break: normal;
  }

  .fab {
    right: 1rem;
    bottom: calc(5rem + env(safe-area-inset-bottom, 0px));
  }

  h1, .h1 { font-size: 2.5rem; }
  h2, .h2 { font-size: 2rem; }
  h3, .h3 { font-size: 1.75rem; }
  h4, .h4 { font-size: 1.5rem; }
  h5, .h5 { font-size: 1.25rem; }
  h6, .h6 { font-size: 1.125rem; }

  article {
    padding: 0.75rem;
    overflow-wrap: anywhere;
  }

  .entry-card .entry-thumb {
    width: 72px;
    height: 72px;
    flex-shrink: 0;
  }

  nav.bottom a span {
    font-size: 0.7rem;
  }

  nav.bottom a i {
    font-size: 1.5rem;
  }

  .grid.middle-align {
    min-height: 70vh !important;
  }

  .field.large, .button.large {
    min-height: 3rem;
  }
}

/* Keep long email addresses from overflowing their card on narrow screens. */
.break-anywhere {
  overflow-wrap: anywhere;
}

/* --- login backdrop --------------------------------------------------------------------------- */

/*
 * A light, decorative horizon - hills, a road, a camper, a sun - fixed to the bottom of the
 * viewport behind the sign-in card (see public/login.html). Every fill is one of the active
 * theme's own custom properties (see fragments/theme-picker.html) at low opacity, so the scene
 * re-tints itself across all five color themes for free and never fights the card for attention.
 *
 * Hidden entirely on the two high-contrast themes - their whole purpose is maximum contrast for
 * low vision, which decorative background art works directly against.
 */
.login-scenery {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  block-size: min(30vh, 280px);
  pointer-events: none;
  overflow: hidden;
  z-index: 0;
}

.login-scenery svg {
  display: block;
  width: 100%;
  height: 100%;
}

body.hc-light .login-scenery,
body.hc-dark .login-scenery {
  display: none;
}

/* --- oauth sign-in buttons ---------------------------------------------------------------------- */

/*
 * Each third-party sign-in button (see public/login.html) is colored to match its own provider's
 * brand rather than the app's theme color - the button itself is standing in for the brand here,
 * same as the logo on it. `.oauth-icon` forces every logo to solid white regardless of the brand
 * color baked into its own source file (see /img/oauth/*.svg) via the brightness/invert filter
 * trick for recoloring an <img> - a brand-colored mark would otherwise vanish against a same-hued
 * button.
 */
.oauth-google { background-color: #4285F4 !important; }
.oauth-facebook { background-color: #0866FF !important; }
.oauth-yahoo { background-color: #6001D2 !important; }

.oauth-icon {
  filter: brightness(0) invert(1);
}

/* --- change-request diff --------------------------------------------------------------------- */

/*
 * The approval review screen's Proposed/Currently-live comparison (see admin/queue/review.html)
 * color-codes what changed using Beer's own .green-text/.red-text/.amber-text - the same hues the
 * status chips elsewhere already use - rather than introducing new ones. This is the one piece
 * those utilities don't cover: a strikethrough for values the proposed change removes, paired with
 * .red-text in the template.
 */
.diff-removed {
  text-decoration: line-through;
}

/* --- branding --------------------------------------------------------------------------------- */

/* Header wordmark. object-fit keeps it from stretching if the browser gets the intrinsic ratio
   wrong before layout settles; margin-right separates it from the tenant/app name next to it. */
.logo-mark {
  width: auto;
  object-fit: contain;
}

/* Positions the copyright .tooltip (Beer CSS's ":hover>.tooltip" needs it as a direct child of the
   hovered, positioned element) and keeps the same spacing .logo-mark had on its own before. */
.logo-mark-wrap {
  position: relative;
  display: inline-flex;
  margin-right: 0.75rem;
}

/* Beer CSS only ships .tooltip.left/.right/.bottom (each still centered on the cross-axis) - the
   copyright note needs to sit below AND to the right of the icon, so it gets its own inset/
   transform pair rather than one of those modifiers. Same specificity and rule shape as Beer's own
   ":hover>.tooltip.bottom" etc, so this app.css rule (loaded after beer.min.css) wins the cascade. */
.tooltip.bottom-right {
  inset: 100% auto auto 100%;
  transform: translate(0.5rem, 0.5rem) scale(0.9);
}

:hover > .tooltip.bottom-right {
  transform: translate(0.5rem, 0.5rem) scale(1);
}

/* --- PWA Install Dialog ----------------------------------------------------------------------- */
#pwa-install-dialog {
  background: var(--surface, #ffffff);
  color: var(--on-surface, #000000);
  max-width: 28rem;
  width: calc(100% - 2rem);
  border-radius: 1.25rem;
  padding: 1.5rem;
  border: 1px solid var(--outline-variant, rgba(127, 127, 127, 0.2));
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4);
}

#pwa-install-dialog h5 {
  margin-top: 0;
  margin-bottom: 1rem;
  color: var(--on-surface, inherit);
}

#pwa-install-dialog::backdrop {
  background: rgba(0, 0, 0, 0.65);
  backdrop-filter: blur(4px);
}

.pwa-step-list {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  margin: 1.25rem 0;
}

.pwa-step-item {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  background: var(--surface-variant, rgba(127, 127, 127, 0.12));
  color: var(--on-surface-variant, inherit);
  padding: 0.85rem 1rem;
  border-radius: 0.75rem;
}

.pwa-step-num {
  font-weight: bold;
  background: var(--primary, #1976d2);
  color: var(--on-primary, #ffffff);
  width: 1.85rem;
  height: 1.85rem;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.875rem;
  flex-shrink: 0;
}

/* "Sample Data" watermark - see SampleTenantBootstrap and GlobalModelAttributes.sampleWatermark.
   Fixed and click-through so it never interferes with the page underneath, tiled diagonally via a
   repeating background image (an inline SVG) rather than many DOM nodes. The tile is sized so the
   full phrase renders once per repeat instead of being cropped mid-word - a smaller tile clipped
   "SAMPLE PARK" down to unreadable fragments like "...LE PARK", which read as noise rather than an
   obvious watermark. */
.sample-watermark {
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='480' height='480'%3E%3Ctext x='240' y='240' font-family='sans-serif' font-size='34' font-weight='bold' letter-spacing='2' fill='%23808080' fill-opacity='0.22' transform='rotate(-30 240 240)' text-anchor='middle' dominant-baseline='middle'%3ESAMPLE DATA%3C/text%3E%3C/svg%3E");
  background-repeat: repeat;
}

/* Resident schedule (scheduler/calendar.html) - extra breathing room between an event's bolded
   name and its "Location: ..." text, which otherwise sit right up against each other inline. */
.event-location {
  margin-inline-start: 1rem;
}
