/*
 * Design tokens
 */

:root {
  /* Brand palette (Oli2): Watermelon as the primary accent, kept distinct
     from Bootstrap's own --bs-danger so destructive actions stay visually
     different from primary ones. Jungle Green/Orange/Sky Aqua below cover
     the success/warning/info roles from the same palette. */
  /* --bs-primary/-rgb stay the same in both themes - they back solid
     surfaces (navbar, .btn-primary) paired with white text, which already
     has enough contrast against either theme. Only -text-emphasis (used
     wherever the accent appears as text on the body background, e.g.
     links and the sidebar's active item) varies per theme, since a color
     dark enough to read on a light page is too dark to read on a dark
     one. */
  --bs-primary: #e84855;
  --bs-primary-rgb: 232, 72, 85;
  --bs-primary-text-emphasis: #8a2632;
  --bs-primary-bg-subtle: #fbdde0;
  --bs-primary-border-subtle: #f0aab1;
  --bs-link-color: var(--bs-primary-text-emphasis);
  /* Derived, not hardcoded: these were fixed shades of Watermelon, so links
     on the other three accents hovered dark red. The -rgb pair only feeds
     rgba() link variants, so tracking --bs-primary-rgb is close enough and,
     unlike a literal, follows the theme. */
  --bs-link-color-rgb: var(--bs-primary-rgb);
  --bs-link-hover-color: color-mix(in srgb, var(--bs-primary-text-emphasis) 70%, black);
  --bs-link-hover-color-rgb: var(--bs-primary-rgb);

  /* Success (Jungle Green) - same consolidation pattern as primary above,
     so it's usable both as a solid surface (calendar's "Available"
     blocks) and as theme-aware text/border via -text-emphasis. */
  --bs-success: #44af69;
  --bs-success-rgb: 68, 175, 105;
  --bs-success-text-emphasis: #317e4c;
  --bs-success-bg-subtle: #dcf0e3;
  --bs-success-border-subtle: #a8dcb9;

  /* Warning (Orange) */
  --bs-warning: #fcab10;
  --bs-warning-rgb: 252, 171, 16;
  --bs-warning-text-emphasis: #8b5e09;
  --bs-warning-bg-subtle: #fef0d6;
  --bs-warning-border-subtle: #fbd58e;

  /* Info (Sky Aqua) - used for the calendar's "today"/now-indicator
     styling (see calendar-widget-script.html), kept separate from primary
     so "today" reads as a distinct marker rather than the brand color. */
  --bs-info: #54c6eb;
  --bs-info-rgb: 84, 198, 235;
  --bs-info-text-emphasis: #1c6a82;
  --bs-info-bg-subtle: #e0f6fc;
  --bs-info-border-subtle: #a6e3f2;

  /* Spacing (4/8 grid) */
  --space-1: .25rem;
  --space-2: .5rem;
  --space-3: .75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-7: 3rem;
  --space-8: 4rem;

  /* Radius & elevation */
  --radius-sm: .25rem;
  --radius-md: .5rem;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .08);
  --shadow-md: 0 4px 12px rgba(0, 0, 0, .12);

  /* Type scale */
  --text-xs: .75rem;
  --text-sm: .8125rem;
  --text-base: .875rem;
  --text-lg: 1rem;
  --text-xl: 1.25rem;
  --text-2xl: 1.5rem;

  /* Motion */
  --duration-fast: 100ms;
  --duration-base: 200ms;
  --ease-out: cubic-bezier(0, 0, .2, 1);

  /* Categorical meeting-type colors */
  --color-solo: #75674b;
  --color-solo-external: #295675;
  --color-internal: #a34726;
  --color-group-external: #54877d;
  --color-hold: #495057;
}

[data-bs-theme="dark"] {
  --bs-primary-text-emphasis: #f5a6ac;
  --bs-primary-bg-subtle: #2e0f12;
  --bs-primary-border-subtle: #8a2632;
  --bs-link-color: var(--bs-primary-text-emphasis);
  /* As in light mode, but hover goes LIGHTER here - on a dark page the link
     colour is already pale, so darkening it would reduce contrast. */
  --bs-link-color-rgb: var(--bs-primary-rgb);
  --bs-link-hover-color: color-mix(in srgb, var(--bs-primary-text-emphasis) 70%, white);
  --bs-link-hover-color-rgb: var(--bs-primary-rgb);

  --bs-success-text-emphasis: #8fd6a8;
  --bs-success-bg-subtle: #0f2417;
  --bs-success-border-subtle: #317e4c;

  --bs-warning-text-emphasis: #f7c869;
  --bs-warning-bg-subtle: #2b1d05;
  --bs-warning-border-subtle: #8b5e09;

  --bs-info-text-emphasis: #8fdcf0;
  --bs-info-bg-subtle: #0d2933;
  --bs-info-border-subtle: #1c6a82;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .4);
  --shadow-md: 0 4px 12px rgba(0, 0, 0, .5);

  /* Lightened so meeting-type labels/borders stay readable on a dark
     surface - the light-theme values are already dark enough to fail
     contrast against a dark body background. */
  --color-solo: #c1bbae;
  --color-solo-external: #9fb3c1;
  --color-internal: #d6ac9d;
  --color-group-external: #b2c9c5;
  --color-hold: #adb5bd;
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --duration-fast: 0ms;
    --duration-base: 0ms;
  }
}

/* Every one of .btn-primary's colors is baked into Bootstrap's compiled
   CSS as a literal hex from its default blue - none of them actually read
   var(--bs-primary) at runtime, despite that variable existing and
   resolving correctly (confirmed via getComputedStyle: --bs-primary is
   #a82e3b on the element, but --bs-btn-bg is still the literal #0d6efd).
   So every color needs overriding by hand, not just the ones that looked
   theme-dependent. */
.btn-primary {
  --bs-btn-bg: var(--bs-primary);
  --bs-btn-border-color: var(--bs-primary);
  /* Darkened from --bs-primary rather than hardcoded. These were literal
     shades of Watermelon (#8f2430 and friends), so on any other accent the
     button turned dark red on hover - green button, red hover. The
     percentages reproduce the weight of those original hand-picked shades,
     so Watermelon looks as it always did while the other themes now darken
     their own colour. */
  --bs-btn-hover-bg: color-mix(in srgb, var(--bs-primary) 62%, black);
  --bs-btn-hover-border-color: color-mix(in srgb, var(--bs-primary) 58%, black);
  --bs-btn-active-bg: color-mix(in srgb, var(--bs-primary) 53%, black);
  --bs-btn-active-border-color: color-mix(in srgb, var(--bs-primary) 49%, black);
  --bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
  --bs-btn-disabled-bg: var(--bs-primary);
  --bs-btn-disabled-border-color: var(--bs-primary);
}

body {
  font-size: .875rem;
}

.feather {
  width: 16px;
  height: 16px;
  vertical-align: text-bottom;
}

/*
 * Sidebar
 */

.sidebar {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  z-index: 100; /* Behind the navbar */
  padding: 48px 0 0; /* Height of navbar */
  box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
}

.sidebar-sticky {
  position: relative;
  top: 0;
  height: calc(100vh - 48px);
  padding-top: .5rem;
  overflow-x: hidden;
  overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
}

@supports ((position: -webkit-sticky) or (position: sticky)) {
  .sidebar-sticky {
    position: -webkit-sticky;
    position: sticky;
  }
}

.sidebar .nav-link {
  font-weight: 500;
  color: var(--bs-body-color);
}

.sidebar .nav-link .feather {
  margin-right: 4px;
  color: var(--bs-secondary-color);
}

.sidebar .nav-link.active {
  color: var(--bs-primary-text-emphasis);
}

.sidebar .nav-link:hover .feather,
.sidebar .nav-link.active .feather {
  color: inherit;
}

.sidebar .nav-link:focus-visible {
  outline: 2px solid var(--bs-primary-text-emphasis);
  outline-offset: 2px;
}

.navbar :is(.nav-link, .navbar-brand, #theme-toggle):focus-visible {
  outline: 2px solid var(--bs-white);
  outline-offset: 2px;
}

.sidebar-heading {
  font-size: .75rem;
  text-transform: uppercase;
}

/*
 * Content
 */

[role="main"] {
  padding-top: 88px; /* Space for fixed navbar */
}

@media (min-width: 576px) {
  [role="main"] {
    padding-top: 48px; /* Space for fixed navbar */
  }
}

/*
 * Navbar
 */

.navbar-brand {
  padding-top: .75rem;
  padding-bottom: .75rem;
  padding-left: var(--space-4);
  padding-right: var(--space-4);
  font-size: 1rem;
}

/* Truly centered regardless of how wide the brand/nav-items either side
   are (a flex layout alone can't do that without them being equal width) -
   .navbar is already position:fixed, which is enough of a containing
   block for this without adding position:relative separately. The badge
   and the video button live inside this group so they stay adjacent and
   move together as a single centered unit. */
.navbar-up-next-group {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

.navbar-up-next {
  background-color: rgba(255, 255, 255, .15);
  font-size: .8125rem;
  white-space: nowrap;
}

.navbar-up-next:hover {
  background-color: rgba(255, 255, 255, .25);
  color: #fff;
}

.navbar-up-next-video {
  width: 28px;
  height: 28px;
  background-color: rgba(255, 255, 255, .15);
  flex-shrink: 0;
}

.navbar-up-next-video svg {
  width: 14px;
  height: 14px;
}

.navbar-up-next-video:hover {
  background-color: rgba(255, 255, 255, .25);
  color: #fff;
}

.bg-accent {
  background-color: var(--bs-primary) !important;
}

.border-accent {
  border-color: var(--bs-primary) !important;
}

/* Text-on-body-surface use of the accent, e.g. icon-only buttons that
   represent a primary (not destructive) action - uses the theme-aware
   emphasis token rather than --bs-primary itself, for the same contrast
   reason .btn-outline-primary below does. */
.text-accent {
  color: var(--bs-primary-text-emphasis) !important;
}

/* Same story as .btn-primary above: .dropdown-item.active's background is
   a literal hardcoded hex in Bootstrap's compiled CSS, not var(--bs-primary). */
.dropdown-item.active,
.dropdown-item:active {
  background-color: var(--bs-primary);
}

/* And again for form checkboxes/radios - --bs-form-check-input-checked-*
   are also literal hex, not derived from --bs-primary. */
.form-check-input:checked {
  background-color: var(--bs-primary);
  border-color: var(--bs-primary);
}

.form-check-input:focus {
  border-color: var(--bs-primary-text-emphasis);
  box-shadow: 0 0 0 .25rem rgba(var(--bs-primary-rgb), .25);
}

/* Same story again: .form-control/.form-select's focus ring is Bootstrap's
   literal hardcoded blue, not var(--bs-primary). */
.form-control:focus,
.form-select:focus {
  border-color: var(--bs-primary-text-emphasis);
  box-shadow: 0 0 0 .25rem rgba(var(--bs-primary-rgb), .25);
}

/* .btn-outline-primary's base (unfilled) state pairs --bs-primary with the
   body background as text/border, which is legible in light mode but not
   dark (see the --bs-primary comment above) - so it uses the emphasis
   token instead. Hover/active still fill with the constant --bs-primary,
   which is fine since white hover text on it is what needs the contrast,
   not the page's body background. */
.btn-outline-primary {
  --bs-btn-color: var(--bs-primary-text-emphasis);
  --bs-btn-border-color: var(--bs-primary-text-emphasis);
  --bs-btn-hover-bg: var(--bs-primary);
  --bs-btn-hover-border-color: var(--bs-primary);
  --bs-btn-active-bg: var(--bs-primary);
  --bs-btn-active-border-color: var(--bs-primary);
  --bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
  --bs-btn-disabled-color: var(--bs-primary-text-emphasis);
  --bs-btn-disabled-border-color: var(--bs-primary-text-emphasis);
}

/* Same fix, same reason, for Bootstrap's other outline variants - each
   pairs its raw theme color with the body background for text/border,
   which has the identical dark-mode contrast problem primary did. Bootstrap
   already ships theme-aware *-text-emphasis tokens for these (unlike
   primary, which is this app's own addition), so no new tokens needed. */
.btn-outline-danger {
  --bs-btn-color: var(--bs-danger-text-emphasis);
  --bs-btn-border-color: var(--bs-danger-text-emphasis);
  --bs-btn-disabled-color: var(--bs-danger-text-emphasis);
  --bs-btn-disabled-border-color: var(--bs-danger-text-emphasis);
}

.btn-outline-success {
  --bs-btn-color: var(--bs-success-text-emphasis);
  --bs-btn-border-color: var(--bs-success-text-emphasis);
  --bs-btn-disabled-color: var(--bs-success-text-emphasis);
  --bs-btn-disabled-border-color: var(--bs-success-text-emphasis);
}

.btn-outline-warning {
  --bs-btn-color: var(--bs-warning-text-emphasis);
  --bs-btn-border-color: var(--bs-warning-text-emphasis);
  --bs-btn-disabled-color: var(--bs-warning-text-emphasis);
  --bs-btn-disabled-border-color: var(--bs-warning-text-emphasis);
}

.navbar .form-control {
  padding: .75rem 1rem;
  border-width: 0;
  border-radius: 0;
}

.brand {
  font-size: 3rem;
  color: #dc3545;
}

/*
 * Account page - theme colour picker
 */

.theme-color-swatch {
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  border: 2px solid transparent;
  padding: 0;
  cursor: pointer;
  position: relative;
}

.theme-color-swatch.active {
  border-color: var(--bs-body-color);
}

.theme-color-swatch.active::after {
  content: "\2713";
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: .9rem;
  font-weight: 700;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .5);
}

.form-control-dark {
  color: #fff;
  background-color: rgba(255, 255, 255, .1);
  border-color: rgba(255, 255, 255, .1);
}

.form-control-dark:focus {
  border-color: transparent;
  box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
}


.hr-divider {
    position: relative;
    font-size: 12px;
    line-height: 20px;
    text-align: center;
    text-transform: uppercase
}

.hr-divider:before {
    position: absolute;
    top: 50%;
    display: block;
    content: "";
    width: 100%;
    height: 1px;
    background-color: var(--bs-border-color)
}

.hr-divider-content {
    position: relative;
    z-index: 2;
    display: inline-block;
    padding-left: 1em;
    padding-right: 1em;
    color: var(--bs-secondary-color);
    vertical-align: middle;
    background-color: var(--bs-body-bg);
    font-weight: 300;
}

.hr-divider-heading {
    margin-top: 0;
    margin-bottom: 0;
    font-size: 130%;
    color: inherit
}


.border-SOLO {
  border: 1px var(--color-solo) solid;
  border-left: 8px var(--color-solo) solid;;
}
.border-SOLO-EXTERNAL {
  border: 1px var(--color-solo-external) solid;
  border-left: 8px var(--color-solo-external) solid;;
}
.border-INTERNAL {
  border: 1px var(--color-internal) solid;
  border-left: 8px var(--color-internal) solid;;
}
.border-GROUP-EXTERNAL {
  border: 1px var(--color-group-external) solid;
  border-left: 8px var(--color-group-external) solid;;
}
.border-HOLD {
  border: 1px var(--color-hold) solid;
  border-left: 8px var(--color-hold) solid;;
}

.text-SOLO {
  color: var(--color-solo);
}
.text-SOLO-EXTERNAL {
  color: var(--color-solo-external);
}
.text-INTERNAL {
  color: var(--color-internal);
}
.text-GROUP-EXTERNAL {
  color: var(--color-group-external);
}
.text-HOLD {
  color: var(--color-hold);
}

.feather-24 {
  width: 24px;
  height: 24px;
}

/* Landing page brand strip.
   Replaces .index-border, which was `border-top: 20px` on <body>: a body
   border sits at the top of the DOCUMENT, so it scrolled out of view. This is
   pinned to the viewport instead and stays put at any scroll position.
   #e84855 is --bs-primary, the brand red. z-index 1030 matches Bootstrap's
   fixed-navbar layer, which keeps it below tooltips so the "Coming soon"
   tooltip still draws on top. */
:root {
  --landing-topbar-height: 52px;
  --landing-topborder-height: 12px;
}

/* Deliberately IN FLOW rather than fixed, so it scrolls away with the page and
   takes the wordmark with it. What stays behind is .landing-topborder below.
   No scroll listener is involved: as the page moves, the red visible at the top
   goes from the full 52px down to the border's 12px on its own, which is the
   shrinking effect, and it cannot fall out of step with the scroll position the
   way a JS-toggled class can. */
.landing-topbar {
  height: var(--landing-topbar-height);
  background-color: #e84855;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* The thick red border that always sticks to the top. Same red as the strip,
   so at scroll 0 the two read as a single band. pointer-events: none so it
   never swallows a click meant for the page underneath. */
.landing-topborder {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--landing-topborder-height);
  background-color: #e84855;
  z-index: 1030;
  pointer-events: none;
}

/* Same wordmark as the post-login navbar brand (base.html's .navbar-brand),
   sized for the strip and forced white for contrast on the red. */
.landing-topbar-brand {
  color: #fff;
  text-decoration: none;
  font-size: 1.25rem;
  line-height: 1;
}

.landing-topbar-brand:hover,
.landing-topbar-brand:focus {
  color: #fff;
}


.all-scroll {
  cursor: all-scroll;
}

.avatar-circle {
  width: 40px;
  height: 40px;
  text-align: center;
  border-radius: 50%;
  -webkit-border-radius: 50%;
  -moz-border-radius: 50%;
}

.initials {
  position: relative;
  top: 10px; /* 25% of parent */
  font-size: 20px; /* 50% of parent */
  line-height: 20px; /* 50% of parent */
  text-align: center;
  font-weight: bold;
}

/* Compact variant for inline rows (e.g. an action item's linked assignee).
   Centers via flexbox rather than .initials' usual top-offset hack (tuned
   for .avatar-circle's 40px and fragile at other sizes) - this guarantees
   the initials sit dead-center regardless of exact font metrics. */
.avatar-circle-sm {
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.avatar-circle-sm .initials {
  position: static;
  top: auto;
  font-size: 11px;
  line-height: 1;
}

.theme-option .theme-check {
  display: none;
}

[data-bs-theme="light"] .theme-option[data-theme-value="light"] .theme-check,
[data-bs-theme="dark"] .theme-option[data-theme-value="dark"] .theme-check {
  display: inline;
}

/* Standalone toggle button (login.html, for logged-out visitors who have
   no Settings dropdown to hold the Light/Dark mode options above). */
#theme-toggle .theme-icon-light {
  display: none;
}

[data-bs-theme="dark"] #theme-toggle .theme-icon-dark {
  display: none;
}

[data-bs-theme="dark"] #theme-toggle .theme-icon-light {
  display: inline;
}

/* Agenda and Action rows share this column grid so the icon, meta field
   and delete control line up vertically between the two sections even
   though each section's rows have a different number of controls
   (Agenda also has an edit-pencil, Action doesn't) - both control columns
   are the same fixed width and flush right, so the delete X always ends
   up in the same place regardless of what else shares its column. */
.meeting-row-icon-col {
  width: 2.5rem;
  flex-shrink: 0;
  display: flex;
  justify-content: center;
}

.meeting-row-meta-col {
  width: 11.25rem;
  flex-shrink: 0;
}

.meeting-row-controls-col {
  width: 4.5rem;
  flex-shrink: 0;
}

.agenda-item, .action-item {
  transition: background-color var(--duration-fast) var(--ease-out);
}

.agenda-item:hover, .action-item:hover {
  background-color: var(--bs-tertiary-bg);
}

.message-log {
  max-height: 25rem;
  display: flex;
  flex-direction: column-reverse;
}

.inline-title-input, .inline-assignee-input {
  border: none;
  background: transparent;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
}

.inline-title-input {
  width: 80%;
  min-width: 200px;
}

.inline-assignee-input {
  width: 100%;
}

.agenda-item-duration-input.inline-assignee-input {
  width: 3rem;
}

.inline-title-input:focus, .inline-assignee-input:focus {
  outline: none;
  box-shadow: none;
  border-bottom: 1px dashed #ccc;
}

/*
 * Sortable / sticky-header tables
 */

.table-sortable thead th[data-sort] {
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}

.table-sortable thead th[data-sort]:hover {
  background-color: var(--bs-tertiary-bg);
}

.table-sortable thead th[data-sort]:focus-visible {
  outline: 2px solid var(--bs-primary-text-emphasis);
  outline-offset: -2px;
}

.table-sortable thead th[data-sort]::after {
  content: "";
  display: inline-block;
  width: 0;
  height: 0;
  margin-left: var(--space-1);
  vertical-align: middle;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-bottom: 5px solid var(--bs-secondary-color);
  opacity: .4;
}

.table-sortable thead th[data-sort][aria-sort="ascending"]::after {
  opacity: 1;
}

.table-sortable thead th[data-sort][aria-sort="descending"]::after {
  border-bottom: none;
  border-top: 5px solid var(--bs-secondary-color);
  opacity: 1;
}

/* Sticks just below the fixed navbar - same breakpoint/offset [role="main"]
   already uses to clear it. */
.table-sticky-header thead th {
  position: sticky;
  top: 88px;
  background-color: var(--bs-body-bg);
  z-index: 10;
}

@media (min-width: 576px) {
  .table-sticky-header thead th {
    top: 48px;
  }
}

/* tui-calendar's own JS sets an explicit inline background-color on
   regular day columns (var(--bs-body-bg), from the theme's
   common.backgroundColor) but leaves narrow (weekend) columns as
   background-color: inherit - against this app's near-white body
   background the border between two explicit same-color columns is
   imperceptible, while the inherited/transparent narrow columns happen
   to show it. Matching the exact inline value (rather than targeting
   .toastui-calendar-column generally) leaves the "today" column's own
   inline highlight tint - a different background-color value - alone.
   !important is required to win over the inline style itself. */
#calendar .toastui-calendar-column[style*="background-color: var(--bs-body-bg)"] {
  background-color: transparent !important;
}

/* Keyboard-highlight for the Timezones dropdown's search-and-arrow-keys
   navigation - distinct from .active (the tick/selected state), since a
   row can be highlighted without being selected and vice versa. Skipped
   when the row is also .active: Bootstrap's own selected style is a dark
   background with white text, and this rule's background alone (equal
   specificity, loaded after Bootstrap's CSS) would otherwise win and
   leave that white text stranded on a light background. */
.free-time-timezone-option.keyboard-highlight:not(.active) {
  background-color: var(--bs-tertiary-bg);
}

/* Person page's GitHub-contributions-style heatmap (contribution_graph.py)
   - weeks as columns, one square per day. A CSS grid rather than fixed
   pixel sizes so the whole thing scales up as the browser widens instead
   of sitting at a constant size - attendee.html emits the label column
   (8 items: a corner spacer + 7 weekday labels) then, for each week, a
   month-label cell followed by 7 day cells, all as one flat sequence;
   grid-auto-flow: column packs that sequence into columns using the 8
   explicit row tracks below, without needing any explicit grid-row/
   grid-column indices on individual cells. Levels are a fixed, absolute
   color scale (not scaled per-person, unlike GitHub's own relative
   scale) so the same color means the same amount of time on every
   person's page. */
.contribution-grid {
  display: grid;
  grid-template-columns: 20px;
  grid-auto-columns: minmax(0, 1fr);
  grid-template-rows: 15px repeat(7, auto);
  grid-auto-flow: column;
  gap: 4px;
  min-width: 300px;
  max-width: 700px;
  overflow-x: auto;
}

.contribution-weekday-label {
  display: flex;
  align-items: center;
  font-size: 0.6rem;
}

.contribution-month-label {
  font-size: 0.65rem;
  white-space: nowrap;
}

.contribution-cell {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  border-radius: 3px;
  background-color: var(--bs-tertiary-bg);
  text-decoration: none;
}

.contribution-cell.level-1 { background-color: color-mix(in srgb, var(--bs-primary) 25%, var(--bs-tertiary-bg)); }
.contribution-cell.level-2 { background-color: color-mix(in srgb, var(--bs-primary) 50%, var(--bs-tertiary-bg)); }
.contribution-cell.level-3 { background-color: color-mix(in srgb, var(--bs-primary) 75%, var(--bs-tertiary-bg)); }
.contribution-cell.level-4 { background-color: var(--bs-primary); }

/* "Today" is the only cell styled differently from its actual time
   level - a bright, unrelated accent color (not a shade of the level
   scale itself) so it pops as a clear "you are here" marker regardless
   of how much time that day happens to have. */
.contribution-cell.contribution-today {
  background-color: var(--bs-warning) !important;
  box-shadow: 0 0 0 1px var(--bs-body-bg);
}

/* ============================================================
   Landing page (index.html) - the pre-login marketing page.

   The hero is scroll-linked: a cluttered week gradually declutters
   into reclaimed free time as the visitor scrolls. index.html's own
   script publishes scroll progress as a single --p custom property
   (0 -> 1) on .landing-hero-track, and every animated piece below
   derives its own state from that one variable - so the whole hero
   costs one style write per frame rather than one per element.

   Deliberately JS-driven rather than using CSS animation-timeline:
   scroll(), which still isn't universally supported - one code path
   that works everywhere beats two that can diverge.
   ============================================================ */

@property --p {
  syntax: '<number>';
  inherits: true;
  initial-value: 0;
}

/* The tall track scrolls past while the panel inside stays pinned;
   that scroll distance is what drives --p from 0 to 1. */
.landing-hero-track {
  position: relative;
  height: 300vh;
}

.landing-hero-panel {
  position: sticky;
  top: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-5);
  padding: var(--space-7) var(--space-4);
  overflow: hidden;
}

/* Grid-stacking (rather than absolute positioning) lets the two
   headline states cross-fade in place while the container still
   sizes itself to the taller of the two. */
.landing-swap {
  display: grid;
  text-align: center;
}

.landing-swap > * {
  grid-area: 1 / 1;
  margin: 0;
}

/* These two ranges deliberately overlap (the outgoing line is still
   fading while the incoming one starts): with a gap instead, there's a
   stretch mid-scroll where neither is visible and the hero looks
   headline-less. */
.landing-headline-before {
  opacity: clamp(0, calc(1 - var(--p) * 2.6), 1);
}

.landing-headline-after {
  opacity: clamp(0, calc((var(--p) - 0.3) * 3.6), 1);
}

.landing-hero-sub {
  max-width: 34rem;
  margin: 0 auto;
}

/* Week grid mockup ------------------------------------------- */

.landing-week {
  width: min(100%, 60rem);
  background-color: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--space-3);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}

.landing-week-head,
.landing-week-body {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
}

.landing-week-head {
  background-color: var(--bs-tertiary-bg);
  border-bottom: 1px solid var(--bs-border-color);
}

.landing-week-head > div {
  padding: var(--space-2) var(--space-3);
  font-size: .75rem;
  font-weight: 600;
  color: var(--bs-secondary-color);
  text-align: center;
}

.landing-week-body {
  /* Hour gridlines, so the mockup reads as a calendar rather than an
     abstract chart - without needing 10 more elements per column. */
  background-image: repeating-linear-gradient(
    to bottom,
    var(--bs-border-color) 0, var(--bs-border-color) 1px,
    transparent 1px, transparent 10%
  );
  height: 44vh;
  min-height: 15rem;
}

.landing-col {
  position: relative;
  border-right: 1px solid var(--bs-border-color);
}

.landing-col:last-child {
  border-right: 0;
}

/* One slot in the week. --t/--h place it (as a % of the 08:00-18:00
   day the grid represents); --d staggers when it animates, so a
   column empties progressively instead of all at once. */
.ls {
  position: absolute;
  left: 5%;
  right: 5%;
  top: calc(var(--t) * 1%);
  height: calc(var(--h) * 1%);
  border-radius: 4px;
  border-left: 3px solid;
  padding: 1px 4px;
  font-size: .625rem;
  line-height: 1.25;
  color: var(--bs-emphasis-color);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* The meetings that clear out - each fades and drifts away once --p
   passes its own staggered threshold. */
.ls-clutter {
  --bp: clamp(0, calc((var(--p) - var(--d) * 0.3) * 3.2), 1);
  opacity: calc(1 - var(--bp));
  transform: translateX(calc(var(--bp) * 10%)) scale(calc(1 - var(--bp) * 0.14));
}

/* The reclaimed time growing into the vacated space - starts later
   than the clutter begins leaving, so it reads as a consequence
   rather than a simultaneous swap. */
.ls-free {
  --bp: clamp(0, calc((var(--p) - 0.4 - var(--d) * 0.18) * 3.6), 1);
  opacity: var(--bp);
  transform: scaleY(var(--bp));
  transform-origin: top center;
  display: flex;
  align-items: center;
  font-weight: 600;
}

.ls-primary {
  background-color: color-mix(in srgb, var(--bs-primary) 22%, var(--bs-body-bg));
  border-left-color: var(--bs-primary);
}

.ls-info {
  background-color: color-mix(in srgb, var(--bs-info) 22%, var(--bs-body-bg));
  border-left-color: var(--bs-info);
}

.ls-warning {
  background-color: color-mix(in srgb, var(--bs-warning) 22%, var(--bs-body-bg));
  border-left-color: var(--bs-warning);
}

.ls-muted {
  background-color: color-mix(in srgb, var(--bs-secondary-color) 18%, var(--bs-body-bg));
  border-left-color: var(--bs-secondary-color);
}

.ls-green {
  background-color: color-mix(in srgb, var(--bs-success) 22%, var(--bs-body-bg));
  border-left-color: var(--bs-success);
  color: var(--bs-success-text-emphasis);
}

/* Reclaimed-hours readout + scroll cue ----------------------- */

.landing-reclaimed {
  opacity: clamp(0, calc((var(--p) - 0.52) * 3.4), 1);
  font-variant-numeric: tabular-nums;
}

.landing-scroll-cue {
  opacity: clamp(0, calc(1 - var(--p) * 5), 1);
  font-size: .8125rem;
}

/* Sections below the hero ----------------------------------- */

.landing-section {
  padding: var(--space-8) var(--space-4);
}

.landing-feature {
  height: 100%;
  padding: var(--space-6);
  background-color: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--space-3);
  transition: transform .2s ease, box-shadow .2s ease;
}

.landing-feature:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

.landing-feature-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  margin-bottom: var(--space-4);
  border-radius: var(--space-2);
  background-color: color-mix(in srgb, var(--bs-primary) 15%, var(--bs-body-bg));
  color: var(--bs-primary);
}

.landing-step-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  margin-bottom: var(--space-3);
  border-radius: 50%;
  background-color: var(--bs-primary);
  color: #fff;
  font-size: .875rem;
  font-weight: 600;
}

/* Fades each section in as it enters the viewport - added by
   index.html's IntersectionObserver, kept separate from the hero's
   --p so ordinary sections don't pay for scroll maths. */
.landing-reveal {
  opacity: 0;
  transform: translateY(1.25rem);
  transition: opacity .6s ease, transform .6s ease;
}

.landing-reveal.is-visible {
  opacity: 1;
  transform: none;
}

/* Short viewports - small laptop windows, or a phone in landscape.
   The hero panel is pinned, so it can't scroll internally: anything
   taller than the viewport is cut off permanently rather than just
   being below the fold. Tighten the spacing and let the grid shrink
   so the whole composition keeps fitting. */
@media (max-height: 700px) {
  .landing-hero-panel {
    gap: var(--space-3);
    padding: var(--space-4);
  }

  .landing-hero-panel h1 {
    font-size: calc(1.4rem + 1.5vw);
  }

  .landing-hero-panel .lead {
    font-size: 1rem;
  }

  .landing-week-body {
    min-height: 11rem;
  }
}

@media (max-height: 520px) {
  .landing-week-body {
    min-height: 9rem;
  }

  /* Least important element here, and by this height it's the
     difference between the calendar fitting and not. */
  .landing-scroll-cue {
    display: none;
  }
}

/* Motion is the whole point of the hero, so rather than freeze it
   half-finished this collapses the scroll track and jumps straight
   to the resolved "week reclaimed" state (see index.html, which sets
   --p to 1) - the same message, delivered statically. */
@media (prefers-reduced-motion: reduce) {
  .landing-hero-track {
    height: auto;
  }

  .landing-hero-panel {
    position: static;
    min-height: 0;
  }

  .landing-scroll-cue {
    display: none;
  }

  .landing-reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .landing-feature:hover {
    transform: none;
  }
}
/* ---------------------------------------------------------------------------
   Dark navbar text for light accent colours.

   The navbar is `navbar-dark ... bg-accent`, so its text is white over
   whatever --bs-primary resolves to. That is fine for the darker accents but
   unreadable on the light ones - measured against #ffffff, Orange (#fcab10)
   gives 1.91:1 and Sky Aqua (#54c6eb) 1.97:1, where WCAG AA asks for 4.5:1.
   The same text in #212529 scores 8.06:1 and 7.83:1.

   Applied by adding .navbar-accent-dark in base.html for those themes.

   !important is needed rather than preferred: Bootstrap's own .text-light and
   .text-white utilities are declared with !important, so no amount of
   specificity beats them. Scoped to .navbar.navbar-accent-dark so it cannot
   affect anything outside this one bar.
   --------------------------------------------------------------------------- */
.navbar.navbar-accent-dark,
.navbar.navbar-accent-dark .navbar-brand,
.navbar.navbar-accent-dark .nav-link,
.navbar.navbar-accent-dark .nav-link.text-light,
.navbar.navbar-accent-dark .nav-link.text-white,
.navbar.navbar-accent-dark .text-light,
.navbar.navbar-accent-dark .text-white,
.navbar.navbar-accent-dark .navbar-up-next,
.navbar.navbar-accent-dark .navbar-up-next-video,
.navbar.navbar-accent-dark .btn-outline-light {
  color: #212529 !important;
}

/* Keep hover/focus dark too - .navbar-up-next:hover sets #fff explicitly, and
   btn-outline-light's hover fills white and switches the label to dark, which
   on a light accent reads as the button vanishing. */
.navbar.navbar-accent-dark .navbar-up-next:hover,
.navbar.navbar-accent-dark .navbar-up-next:focus,
.navbar.navbar-accent-dark .navbar-up-next-video:hover,
.navbar.navbar-accent-dark .text-light:hover,
.navbar.navbar-accent-dark .nav-link:hover,
.navbar.navbar-accent-dark .nav-link:focus,
.navbar.navbar-accent-dark .btn-outline-light:hover,
.navbar.navbar-accent-dark .btn-outline-light:focus,
.navbar.navbar-accent-dark .btn-outline-light:active {
  color: #212529 !important;
}

/* The translucent white pill/circle behind "Up next" reads as a pale smudge on
   a light accent; a translucent black gives the same subtle lift. */
.navbar.navbar-accent-dark .navbar-up-next,
.navbar.navbar-accent-dark .navbar-up-next-video {
  background-color: rgba(0, 0, 0, .12);
}

.navbar.navbar-accent-dark .navbar-up-next:hover,
.navbar.navbar-accent-dark .navbar-up-next-video:hover {
  background-color: rgba(0, 0, 0, .2);
}

.navbar.navbar-accent-dark .btn-outline-light:hover,
.navbar.navbar-accent-dark .btn-outline-light:focus {
  background-color: rgba(0, 0, 0, .12);
}

/* The hamburger/collapse toggle is a background-image, so colour has no
   effect on it - swap in the dark variant of Bootstrap's own icon. */
.navbar.navbar-accent-dark .navbar-toggler-icon {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
}

/* Feather icons are strokes, so they follow currentColor - but the spinner
   border needs telling explicitly. */
.navbar.navbar-accent-dark .spinner-border {
  border-color: #212529;
  border-right-color: transparent;
}
