/**
 * Dashboard Buddy — shared styles for every dashboard section.
 *
 * 61876 key #2 is "reuse CSS as much as possible". The reuse target is THIS
 * FILE, not the existing mu-plugin CSS: sections are written fresh against
 * the tokens and primitives below rather than porting the old rules over.
 * The one thing carried across from the old code is the COLOUR PALETTE, so
 * the revamped dashboard still looks like the site.
 *
 * COLOURS + TYPE — measured off the rendered dashboard on dev (computed
 * styles, not source greps), so these match what a student actually sees.
 *
 * The source CSS has drifted badly — nine different reds are in use
 * (#e23b3b #a32b2b #c22a2a #cc3636 #db1d3f #e11d3d #e74040 #dc3545
 * #f44336) and six near-blacks (#000 #111 #1a1a1a #222 #232323 #262626) —
 * which is why these were taken from the render rather than the source.
 * Everything in this plugin resolves to the tokens below.
 *
 * Still unmeasured, carried over from frequency counts: --db-accent-hover,
 * --db-surface-raised, --db-highlight and the overlays. They need a hover
 * state and a progress element in view to sample properly.
 *
 * The child theme defines no custom properties of its own, so there was
 * nothing upstream to inherit — these are the first.
 *
 * BREAKPOINTS — the mu-plugins use at least a dozen values (450, 691, 750,
 * 767, 768, 900, 969, 1000, 1023, 1200 …). Canonical set, the two most-used
 * existing values plus a small-phone stop:
 *
 *   sm   max-width: 480px    small phone
 *   md   max-width: 768px    phone / small tablet   (most common today)
 *   lg   max-width: 1023px   tablet / small laptop  (next most common)
 *
 * Sections should use these three and nothing else. CSS custom properties
 * cannot be used inside a media query, so the values are literal below —
 * this comment is the source of truth.
 *
 * Note: the existing markup renders separate desktop and mobile variants
 * (e.g. .dashboard-discover-layout vs .dashboard-discover-layout-mobile).
 * Sections here collapse to ONE responsive markup tree (key #1).
 */

.dashboard-buddy,
.dashboard-buddy-tabs {
  /* Colour — see the drift note above before adding a new value here. */
  --db-accent: #e23b3b;         /* brand red, primary action */
  --db-accent-hover: #a32b2b;   /* its hover/pressed pair */
  --db-highlight: #ffd54f;      /* amber, for emphasis/progress accents */

  --db-bg: #232323;             /* page ground (measured: body) */
  --db-bg-content: #1c1c1c;     /* content wrap, slightly darker than body */
  --db-surface: #222;           /* section/card surface (measured: card) */
  --db-surface-raised: #262626; /* hovered or lifted card */

  --db-text: #fff;
  --db-text-muted: #828282;
  --db-border: #e0e0e0;

  --db-overlay: rgba(0, 0, 0, 0.3);   /* thumbnail scrims */
  --db-overlay-strong: rgba(0, 0, 0, 0.9);

  /* Placeholder mark for cards with no reachable image. Drawn as a mask so
   * it takes a palette colour — the source SVG is #C32139, which would
   * otherwise have been yet another red in a file that already documents
   * nine of them. */
  --db-placeholder-mark: var(--db-accent);
  --db-placeholder-bg: var(--db-bg-content);

  /* Type — the theme pairs a condensed display face with a body sans. */
  --db-font-heading: "Bebas Neue", "Lato", sans-serif;
  --db-font-body: "Lato", sans-serif;
  --db-heading-size: 32px;      /* measured: section headings */
  --db-card-title-size: 24px;   /* measured: card titles */
  --db-subheading-size: 20px;   /* headings inside a split column */

  /* Body scale. THREE sizes, and nothing outside them.
   *
   * There were five (18/14/13/12/11) doing one job, four of them muted
   * secondary text with no rule for which. 13 and 11 were not chosen at
   * all - they are 0.8125rem and 0.6875rem, i.e. rem arithmetic showing
   * through. Everything is px here so the numbers are the numbers.
   *
   * Tracking is one value or none: the 0.4px and 0.52px that were in here
   * came from 0.02em and 0.04em set at different moments. */
  --db-text-lg: 18px;           /* tile descriptions */
  --db-text-md: 14px;           /* card meta, instructor */
  --db-text-sm: 12px;           /* progress label, last activity */
  --db-tracking: 0.03em;

  /* Spacing scale */
  --db-gap-sm: 8px;
  --db-gap: 16px;
  --db-gap-lg: 24px;

  /* Section rhythm */
  --db-section-gap: 32px;

  /* Cards */
  --db-radius: 18px;            /* measured: card corner */
  --db-card-min: 220px;
  --db-card-border-width: 1px;
  --db-card-border-color: var(--db-accent);   /* DC red */

  display: block;
  margin: 0 0 var(--db-section-gap);
  font-family: var(--db-font-body);
  color: var(--db-text);
}

/* Sections that merely WRAP another plugin's shortcode opt out of this.
 * The reset is right for markup this plugin authors and wrong to impose on
 * markup it doesn't: content written against content-box shrinks under
 * border-box wherever an explicit height meets padding. */
.dashboard-buddy:not(.is-passthrough) *,
.dashboard-buddy:not(.is-passthrough) *::before,
.dashboard-buddy:not(.is-passthrough) *::after {
  box-sizing: border-box;
}

/* The rail is markup this plugin authors, so it takes the same reset. Scoped
 * to the rail rather than the whole tab set: the PANELS hold other people's
 * shortcodes, and imposing border-box on those is the thing the exclusion
 * above exists to avoid. */
.dashboard-buddy-tabs__nav,
.dashboard-buddy-tabs__nav *,
.dashboard-buddy-tabs__nav *::before,
.dashboard-buddy-tabs__nav *::after {
  box-sizing: border-box;
}

/* ---------------------------------------------------------------------------
 * Section chrome
 * ------------------------------------------------------------------------ */

/* Header row: heading left, actions anchored to the right edge. */
.dashboard-buddy__header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--db-gap);
  margin: 0 0 var(--db-gap);
}

.dashboard-buddy__heading {
  margin: 0;
  font-family: var(--db-font-heading);
  font-size: var(--db-heading-size);
  line-height: 1.1;
  color: var(--db-text);
}

.dashboard-buddy__toggle {
  margin-left: auto;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  font-family: var(--db-font-heading);
  font-size: var(--db-toggle-size, var(--db-subheading-size));
  font-weight: 400;
  letter-spacing: var(--db-tracking);
  color: var(--db-accent);
  white-space: nowrap;
}

.dashboard-buddy__toggle:hover,
.dashboard-buddy__toggle:focus-visible {
  color: var(--db-accent-hover);
}

/* The way back out of a long expanded row, so a student who has just
 * opened sixty cards does not have to scroll to the top to close them.
 *
 * Visible only while the row is open. :has() rather than a class the
 * script maintains, so the button's state cannot drift from the row's —
 * one source of truth. Browsers without :has() simply never show it,
 * which loses a convenience rather than breaking anything. */
.dashboard-buddy__toggle--foot {
  display: none;
  width: 100%;
  margin: var(--db-gap) 0 0;
  text-align: center;
}

.dashboard-buddy:has(.dashboard-buddy__row.is-expanded) .dashboard-buddy__toggle--foot {
  display: block;
}

.dashboard-buddy__body {
  position: relative;
}

/* ---------------------------------------------------------------------------
 * Layout primitives — sections compose these instead of rolling their own.
 * ------------------------------------------------------------------------ */

/* Auto-fitting card grid. Responsive with no media query at all: cards wrap
 * on their own min width. Prefer this over a fixed column count. */
/* --db-card-min is the AUTHOR's value: it comes from the element's WP Bakery
 * setting, written as an inline custom property on the row. An inline value
 * beats anything a media query can say about the same property, so the
 * breakpoints must not try to shrink it directly — they cap it instead, and
 * min() takes whichever is smaller. Setting --db-card-min per breakpoint
 * looks like it works and silently does nothing whenever the element
 * specifies its own. */
/* auto-FILL, not auto-fit.
 *
 * auto-fit collapses the tracks it did not fill, so the surviving cards
 * split the whole row between them: at 960px one card became 960px wide
 * and two became 472px each, against the 228px they take when the row is
 * full. With an aspect-ratio image on top, a lone card turned into a
 * banner. auto-fill keeps the empty tracks, so a card is the same size
 * whether the section holds one or nine, and a short row simply ends
 * early. The two are identical once the row IS full. */
.dashboard-buddy__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(var(--db-card-min), var(--db-card-cap, 9999px)), 1fr));
  gap: var(--db-gap);
}

/* Revealed cards fade and slide in as the row opens. The row's own height
 * is animated by the script — a grid row has no height to transition to
 * until its cards are shown, so that part has to be measured rather than
 * expressed here.
 *
 * Skipped entirely for anyone who has asked for less motion. */
@media (prefers-reduced-motion: no-preference) {
  .dashboard-buddy__row.is-clamped.is-expanded > .is-overflow,
  .dashboard-buddy__row.is-clamped.is-expanded > .is-trimmed {
    animation: dashboard-buddy-reveal 260ms ease both;
  }
}

@keyframes dashboard-buddy-reveal {
  from {
    opacity: 0;
    transform: translateY(-8px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

/* A card left alone on a second row is trimmed by the script, which marks
 * it .is-trimmed. CSS cannot count grid columns, and guessing the width at
 * which three fit instead of four was wrong on a real iPad — so the column
 * count is measured rather than predicted. Expanding drops the rule and
 * the card returns. */
.dashboard-buddy__row.is-clamped:not(.is-expanded) > .is-trimmed {
  display: none;
}

/* A clamped row shows a fixed NUMBER of cards, not a fixed number of rows.
 * The server marks everything past the cap .is-overflow and this hides it
 * until .is-expanded. Four visible reads the same on every screen — it is
 * one row of four on a laptop and two rows of two on a phone, rather than
 * "however many happen to fit", which varied from 2 to 4.
 *
 * display is only ever removed, never set, so expanding restores whatever
 * the element's own rule gives it. */
.dashboard-buddy__row.is-clamped:not(.is-expanded) > .is-overflow {
  display: none;
}

/* Card: picture on top, text below. Column flex so the meta line can be
 * pushed to the bottom; grid stretch makes every card in a row the same
 * height, so those meta lines align across the row. */
.dashboard-buddy__card {
  display: flex;
  flex-direction: column;
  height: 100%;
  background: var(--db-surface);
  border: var(--db-card-border-width) solid var(--db-card-border-color);
  border-radius: var(--db-radius);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
}

.dashboard-buddy__card:hover,
.dashboard-buddy__card:focus-visible {
  background: var(--db-surface-raised);
  border-color: var(--db-accent-hover);
}

.dashboard-buddy__card-text {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: var(--db-gap-sm);
  padding: var(--db-gap);
}

.dashboard-buddy__card-title {
  margin: 0;
  font-family: var(--db-font-heading);
  font-size: var(--db-card-title-size);
  line-height: 1.1;
  color: var(--db-text);
}

.dashboard-buddy__card-instructor {
  margin: 0;
  font-size: var(--db-text-md);
  letter-spacing: var(--db-tracking);
  text-transform: uppercase;
  color: var(--db-text);
}

/* Anchored to the bottom of the card, whatever the title wraps to. */
.dashboard-buddy__card-meta {
  display: flex;
  align-items: center;
  gap: var(--db-gap-sm);
  margin: auto 0 0;
  color: var(--db-text-muted);
  font-size: var(--db-text-md);
}

/* Course progress, appended to the standard card by the progress section.
 * Sits below the meta line, so the card keeps its shape and the bar is the
 * only thing that section adds. */
.dashboard-buddy__progress {
  display: flex;
  align-items: center;
  gap: var(--db-gap-sm);
}

.dashboard-buddy__progress-bar {
  flex: 1;
  height: 6px;
  border-radius: 3px;
  background: var(--db-overlay);
  overflow: hidden;
}

.dashboard-buddy__progress-bar span {
  display: block;
  height: 100%;
  background: var(--db-accent);
}

.dashboard-buddy__progress-label {
  margin: 0;
  font-size: var(--db-text-sm);
  color: var(--db-text-muted);
  white-space: nowrap;
}

.dashboard-buddy__activity {
  margin: 0;
  font-size: var(--db-text-sm);
  color: var(--db-text-muted);
}

/* The bookmark ribbon in the corner of a saved card's thumbnail. */
.dashboard-buddy__bookmark-flag {
  position: absolute;
  top: 0;
  left: var(--db-gap);
  width: 18px;
  height: 24px;
  background: var(--db-text);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 78%, 0 100%);
}

/* Media block that keeps its aspect ratio as the grid reflows. */
.dashboard-buddy__thumb {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--db-overlay);
}

.dashboard-buddy__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Branded placeholder: the DC mark centred on a flat ground. Masked rather
 * than used as an <img>, so the colour comes from the palette and one file
 * serves any future recolouring. Costs no per-card request — it is one
 * cached asset shared by every placeholder on the page. */
/* Shared by the card thumb and the video trigger. The rule used to name
 * __thumb only, so a trigger with no poster rendered as a flat rectangle —
 * the placeholder existed but nothing on the trigger ever matched it. */
.dashboard-buddy__thumb.is-placeholder,
.dashboard-buddy__video-trigger.is-placeholder {
  background: var(--db-placeholder-bg);
}

.dashboard-buddy__thumb.is-placeholder::after,
.dashboard-buddy__video-trigger.is-placeholder::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--db-placeholder-mark);
  opacity: 0.55;
  -webkit-mask: url("../img/dc-mark.svg") no-repeat center / 34%;
  mask: url("../img/dc-mark.svg") no-repeat center / 34%;
}

/* On a trigger the play control is also centred, so the mark steps back to
 * being a backdrop rather than competing with it: bigger, dimmer, and
 * underneath. ::before, not ::after, so the two never fight for the slot. */
.dashboard-buddy__video-trigger.is-placeholder::before {
  opacity: 0.28;
  -webkit-mask-size: 55%;
  mask-size: 55%;
}

/* Responsive video box. Replaces the padding-bottom:56.25% hack the DCTV
 * block carried inline; aspect-ratio does the same job and lets the ratio
 * be a setting rather than a magic percentage. */
.dashboard-buddy__embed {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: var(--db-radius);
  background: var(--db-bg-content);
}

.dashboard-buddy__embed iframe,
.dashboard-buddy__embed object,
.dashboard-buddy__embed embed {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.dashboard-buddy__schedule {
  margin-top: var(--db-gap);
}

/* Live indicator, sat against the section heading. The original used a
 * #F25452 dot — an eleventh red on this site — so it takes the palette
 * colour instead, overridable via --db-live. */
.dashboard-buddy__live {
  display: inline-block;
  width: 9px;
  height: 9px;
  margin-right: var(--db-gap-sm);
  border-radius: 50%;
  background: var(--db-live, var(--db-accent));
  vertical-align: middle;
}

/* Two-column row, used by the video + newest-card element.
 *
 * The outer element exists only to be the query container — a container
 * query cannot size the element it is declared on, so the grid must be its
 * child. Stacking is keyed to the CONTAINER because the dashboard has a
 * left sidebar, which makes viewport width a poor guide to how much room
 * the row actually has. Browsers without container query support keep the
 * side-by-side ratio at every width, which degrades acceptably. */
.dashboard-buddy__split-outer {
  container-type: inline-size;
}

.dashboard-buddy__split {
  display: grid;
  grid-template-columns: var(--db-split-cols, 1fr 1fr);
  align-items: start;
  /* Literal fallback: an undefined custom property makes the whole
   * declaration invalid, and the columns would then touch. */
  gap: var(--db-gap-lg, 24px);
}

@container (max-width: 700px) {
  .dashboard-buddy__split {
    grid-template-columns: 1fr;
  }
}

/* The smaller heading used inside a split column, where the section's own
 * 32px heading would overpower a third of the row. */
.dashboard-buddy__subheading {
  margin: 0 0 var(--db-gap);
  font-family: var(--db-font-heading);
  font-size: var(--db-subheading-size);
  line-height: 1.1;
  color: var(--db-text);
}

/* Min-width 0 so a grid column may shrink below its content's intrinsic
 * width — without it a long card title can force the column wider and
 * break the ratio. */
.dashboard-buddy__split-media,
.dashboard-buddy__split-aside {
  min-width: 0;
}

/* Height-matched split: the row's height comes from the player, and the
 * aside fills it rather than growing past it.
 *
 * min-height: 0 on the column and the card is what makes this work — a
 * flex item defaults to min-height:auto and refuses to shrink below its
 * content, which would let the card push the row taller than the video. */
.dashboard-buddy__split.is-matched {
  align-items: stretch;
}

.dashboard-buddy__split.is-matched .dashboard-buddy__split-aside {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* Side by side, the heading is dropped: the card's whole job here is to
 * match the player's height, and a heading above it spends that height on
 * a label the video does not have. Restored once the row stacks, where the
 * card gets a row to itself and needs saying what it is. */
.dashboard-buddy__split.is-matched .dashboard-buddy__subheading {
  display: none;
}

@container (max-width: 700px) {
  .dashboard-buddy__split.is-matched .dashboard-buddy__subheading {
    display: block;
  }
}

.dashboard-buddy__card--fill {
  flex: 1;
  min-height: 0;
}

/* The image takes whatever height the text leaves, instead of holding a
 * fixed 16:9 as it does in a card grid. */
.dashboard-buddy__card--fill .dashboard-buddy__thumb {
  flex: 1;
  min-height: 0;
  aspect-ratio: auto;
}

.dashboard-buddy__card--fill .dashboard-buddy__card-text {
  flex: 0 0 auto;
}

/* Stacked, there is no video height to match, so the image goes back to a
 * sensible ratio rather than collapsing. */
@container (max-width: 700px) {
  .dashboard-buddy__card--fill .dashboard-buddy__thumb {
    flex: 0 0 auto;
    aspect-ratio: 16 / 9;
  }
}

/* Nav tiles — the discover section. Their own layout, not the content
 * card: icon left, text right, chevron at the end. They point at features
 * rather than posts, so forcing them into the card grid would make four
 * small nav items read like lessons. They do share the card's surface,
 * border and corner so the dashboard still looks like one thing. */
.dashboard-buddy__tiles {
  display: grid;
  /* auto-fill for the same reason as the card grid above. */
  grid-template-columns: repeat(auto-fill, minmax(var(--db-tile-min, 220px), 1fr));
  gap: var(--db-gap);
}

/* Stacked, not icon-beside-text.
 *
 * The row layout needed ~260px per tile, which fit only two across the
 * 757px the dashboard column actually gives this grid — leaving the third
 * orphaned on its own row. Stacking frees the full tile width for the
 * description instead of leaving it a ~100px column, so three fit
 * comfortably. */
.dashboard-buddy__tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--db-gap-sm);
  padding: var(--db-gap);
  background: var(--db-surface);
  border: var(--db-card-border-width) solid var(--db-card-border-color);
  border-radius: var(--db-radius);
  text-decoration: none;
  color: inherit;
}

.dashboard-buddy__tile:hover,
.dashboard-buddy__tile:focus-visible {
  background: var(--db-surface-raised);
  border-color: var(--db-accent-hover);
}

.dashboard-buddy__tile-icon {
  flex: 0 0 auto;
  display: flex;
  width: 40px;
  height: 40px;
  align-items: center;
  justify-content: center;
}

.dashboard-buddy__tile-icon img {
  max-width: 100%;
  max-height: 100%;
}

.dashboard-buddy__tile-body {
  /* Full tile width now that nothing sits beside it. Right padding keeps
   * the text clear of the chevron in the corner. */
  width: 100%;
  min-width: 0;
  padding-right: 24px;
}

.dashboard-buddy__tile-title {
  display: block;
  font-family: var(--db-font-heading);
  font-size: var(--db-subheading-size);
  line-height: 1.1;
  color: var(--db-text);
}

.dashboard-buddy__tile-text {
  margin: 4px 0 0;
  /* Matches the learning resources columns these tiles sit alongside. */
  font-size: var(--db-text-lg);
  color: var(--db-text-muted);
}

/* Chevron, drawn rather than shipped as four copies of the same inline SVG
 * the way the mu-plugin did. Parked in the corner now the tile stacks, so
 * it costs no width. */
.dashboard-buddy__tile-arrow {
  position: absolute;
  top: var(--db-gap);
  right: var(--db-gap);
  width: 10px;
  height: 10px;
  border-top: 2px solid var(--db-accent);
  border-right: 2px solid var(--db-accent);
  transform: rotate(45deg);
}

/* Empty state, shared so every section reports "nothing here" the same way. */
.dashboard-buddy__empty {
  padding: var(--db-gap-lg);
  text-align: center;
  color: var(--db-text-muted);
}

/* ---------------------------------------------------------------------------
 * Responsive — the three canonical stops.
 * ------------------------------------------------------------------------ */

@media (max-width: 1023px) {
  .dashboard-buddy {
    --db-card-cap: 200px;
    --db-section-gap: 28px;
  }
}

@media (max-width: 768px) {
  .dashboard-buddy {
    --db-card-cap: 160px;
    --db-gap: 12px;
    --db-section-gap: 24px;
  }

  .dashboard-buddy__heading {
    --db-heading-size: 24px;
  }

}

@media (max-width: 480px) {
  .dashboard-buddy {
    --db-gap: 10px;
  }

  /* One card across on a phone. Stated as a column count rather than a
   * width cap: at phone widths the auto-fill maths will happily fit two
   * narrow cards, and two is too cramped to read. Setting the template
   * directly also sidesteps the inline --db-card-min the element writes,
   * which no media query can override. */
  .dashboard-buddy__grid {
    grid-template-columns: 1fr;
  }
}

/* Clip caption in the split column — the Daily Short or trailer sitting
 * beside DCTV. Its own block rather than the card's, because there is no
 * card here: the player is the content and this is just its label. */
.dashboard-buddy__clip-text {
  margin-top: var(--db-gap);
}

/* Sits above the player. It names the slot, not the clip, so it reads as
 * a small section heading rather than a caption. */
.dashboard-buddy__clip-label {
  margin: 0 0 var(--db-gap-sm);
  font-family: var(--db-font-heading);
  /* Twice the body size it used to be. Written as the calculation rather
   * than 28px so it stays tied to the token, and because 28 is not a step
   * on the scale this file otherwise keeps to (32/24/20/18/14/12). */
  font-size: calc(var(--db-text-md) * 2);
  line-height: 1.1;
  letter-spacing: var(--db-tracking);
  text-transform: uppercase;
  text-align: right;
  color: var(--db-accent);
}

.dashboard-buddy__clip-text .dashboard-buddy__card-title {
  margin: 0 0 var(--db-gap-sm);
}

.dashboard-buddy__clip-text .dashboard-buddy__card-meta {
  margin: 0;
}

/* ---------------------------------------------------------------------------
 * Video modal — both players in the DCTV split open here rather than
 * embedding inline, so neither third-party iframe is fetched until asked
 * for. The script builds the iframe on open and removes it on close.
 * ------------------------------------------------------------------------ */

/* The poster. A button, not a div with a handler: it is focusable, it
 * announces itself, and Enter and Space work without any JS of ours. */
.dashboard-buddy__video-trigger {
  display: block;
  position: relative;
  width: 100%;
  padding: 0;
  border: 0;
  cursor: pointer;
  background: var(--db-placeholder-bg);
}

.dashboard-buddy__video-trigger img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Play control, drawn rather than shipped as an asset. */
.dashboard-buddy__play {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 56px;
  height: 56px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: var(--db-accent);
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.45);
  z-index: 1;
}

.dashboard-buddy__play::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 54%;
  width: 0;
  height: 0;
  transform: translate(-50%, -50%);
  border-left: 16px solid var(--db-text);
  border-top: 10px solid transparent;
  border-bottom: 10px solid transparent;
}

.dashboard-buddy__video-trigger:hover .dashboard-buddy__play,
.dashboard-buddy__video-trigger:focus-visible .dashboard-buddy__play {
  background: var(--db-accent-hover);
}

.dashboard-buddy-modal[hidden] {
  display: none;
}

.dashboard-buddy-modal {
  position: fixed;
  inset: 0;
  z-index: 100000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--db-gap);
  background: var(--db-overlay-strong, rgba(0, 0, 0, 0.9));
}

.dashboard-buddy-modal__box {
  width: min(1100px, 92vw);
  background: var(--db-surface, #222);
  border: 1px solid var(--db-accent, #e23b3b);
  border-radius: var(--db-radius, 18px);
  overflow: hidden;
}

/* Close button only — the dialog carries no visible title. */
.dashboard-buddy-modal__head {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: var(--db-gap-sm) var(--db-gap);
}

.dashboard-buddy-modal__close {
  border: 0;
  background: none;
  cursor: pointer;
  font-size: 28px;
  line-height: 1;
  color: var(--db-text, #fff);
}

.dashboard-buddy-modal__close:hover,
.dashboard-buddy-modal__close:focus-visible {
  color: var(--db-accent, #e23b3b);
}

/* 16:9, and the iframe fills it. */
.dashboard-buddy-modal__frame {
  position: relative;
  aspect-ratio: 16 / 9;
  background: #000;
}

.dashboard-buddy-modal__frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* Stop the page scrolling behind the dialog. */
body.has-dashboard-buddy-modal {
  overflow: hidden;
}

/* ---------------------------------------------------------------------------
 * TAB RAIL
 *
 * Replaces the [mpc_tabs] widget. The layout exists to make one thing
 * possible that could not be done from outside that widget: a rail that
 * follows the scroll and STOPS AT THE END OF THE CONTENT.
 *
 * That is the whole trick, and it is two declarations. The rail and the
 * panels are grid siblings, so the grid is as tall as the panels; align-items
 * start keeps the rail its own height instead of stretching to match, which
 * is what leaves it room to move; position sticky then does the rest. The
 * previous rail used position:fixed, which has no relationship to the
 * content at all — hence the scroll listener that faded it out when it
 * reached the footer.
 *
 * Nothing here may introduce an overflow:hidden between the rail and the
 * grid. That silently kills sticky, and the failure looks like a CSS typo.
 * ------------------------------------------------------------------------ */

/* THE ONE RULE THE WHOLE THING RESTS ON.
 *
 * The theme sets overflow-x: hidden on <body> and on #page. That is not
 * inert: a box with one axis hidden and the other visible computes the
 * visible axis to auto, which makes the box a SCROLL CONTAINER. A sticky
 * element sticks relative to its nearest scroll container, so the rail was
 * being asked to stick inside a box that never scrolls — and therefore
 * never stuck, at any width. Nothing about the rail's own CSS was wrong,
 * which is exactly why this is worth spelling out.
 *
 * overflow-x: clip stops the same horizontal overflow the theme is guarding
 * against WITHOUT creating a scroll container.
 *
 * Scoped to pages that carry a rail, via a body class, so no other page's
 * scrolling changes. #page is BuddyBoss's wrapper — named here because that
 * is the element with the property, and a comment is a cheaper coupling
 * than a JavaScript walk up the tree at load.
 */
.has-dashboard-buddy-tabs,
.has-dashboard-buddy-tabs #page {
  overflow-x: clip;
}

.dashboard-buddy-tabs {
  /* Set inline from the element's settings; these are the fallbacks. */
  --db-tabs-offset: 66px;   /* clearance for the site's fixed header */
  --db-tabs-rail: 170px;

  --db-tabs-top: var(--db-tabs-offset);

  display: grid;
  grid-template-columns: var(--db-tabs-rail) minmax(0, 1fr);
  gap: var(--db-gap-lg);
  align-items: start;
  font-family: var(--db-font-body);
}

/* The admin bar sits above everything and is not part of the offset the
 * editor sets, so it is added here rather than baked into that number. */
.admin-bar .dashboard-buddy-tabs {
  --db-tabs-top: calc(var(--db-tabs-offset) + 32px);
}

@media screen and (max-width: 782px) {
  .admin-bar .dashboard-buddy-tabs {
    --db-tabs-top: calc(var(--db-tabs-offset) + 46px);
  }
}

.dashboard-buddy-tabs__nav {
  position: sticky;
  top: var(--db-tabs-top);
  /* Own scrollbar once there are more tabs than screen. Without this a long
   * rail is simply cut off at the bottom of the viewport. */
  max-height: calc(100svh - var(--db-tabs-top) - var(--db-gap));
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  /* Room to the LEFT for the open-tab marker, without moving the labels:
   * the padding makes space inside the box and the negative margin takes
   * it back off the layout. Needed because scrolling on one axis clips the
   * other, so a marker sitting at a negative offset is simply cut off. */
  padding-left: 12px;
  margin-left: -12px;
}

.dashboard-buddy-tabs.is-static .dashboard-buddy-tabs__nav {
  position: static;
  max-height: none;
  overflow: visible;
}

.dashboard-buddy-tabs__list {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.dashboard-buddy-tabs__link {
  position: relative;
  display: flex;
  align-items: center;
  gap: 20px;
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  box-shadow: none;
  font-family: var(--db-font-body);
  font-size: var(--db-text-lg);
  font-weight: 400;
  line-height: 1.2;
  text-align: left;
  text-transform: capitalize;
  text-decoration: none;
  color: var(--db-text-muted);
  cursor: pointer;
  transition: color 0.2s ease;
}

.dashboard-buddy-tabs__link:hover,
.dashboard-buddy-tabs__link:focus-visible {
  color: var(--db-text);
  text-decoration: none;
}

.dashboard-buddy-tabs__link[aria-current="true"] {
  color: var(--db-text);
}

/* The open tab had NO marking at all on the old rail — every item was the
 * same colour, including the one you were looking at. */
.dashboard-buddy-tabs__link[aria-current="true"]::before {
  content: "";
  position: absolute;
  top: -2px;
  bottom: -2px;
  left: -12px;
  width: 3px;
  border-radius: 2px;
  background: var(--db-accent);
}

/* Icons come in two kinds — see Dashboard_Buddy_Icons. Both are sized here
 * so a Font Awesome glyph and one of our SVGs occupy the same box and the
 * labels line up regardless of which a tab uses. */
.dashboard-buddy-tabs__icon {
  flex: 0 0 20px;
  width: 20px;
  height: 20px;
  font-size: var(--db-text-lg);
  line-height: 20px;
  text-align: center;
}

/* Masked rather than an <img>, so the icon takes the link's colour and
 * inherits the hover and open states for nothing. */
.dashboard-buddy-tabs__icon--svg {
  background-color: currentColor;
  -webkit-mask: var(--db-icon) no-repeat center / contain;
  mask: var(--db-icon) no-repeat center / contain;
}

.dashboard-buddy-tabs__label {
  min-width: 0;
}

.dashboard-buddy-tabs__panel[hidden] {
  display: none;
}

/* Below the tablet stop the rail moves above the content as a band that
 * WRAPS. It stays sticky there: the reason to have a rail at all is to be
 * able to change section without scrolling back for it.
 *
 * It was a horizontal scroller first, which put half the sections off the
 * edge of the screen behind a gesture nobody is told about. Every item is
 * on show now, over as many lines as that takes, and the type and gaps
 * tighten as the screen narrows to keep the band shallow. */
@media screen and (max-width: 1023px) {
  /* BLOCK, not a one-column grid.
   *
   * A sticky grid item is confined to its GRID AREA, and stacking the rail
   * above the content puts it in a row of its own — a row exactly as tall
   * as the rail, so it has nowhere to travel and scrolls away with the
   * page. As a block container the rail's containing block is the whole tab
   * set, which is as tall as the open panel, and it sticks for the length
   * of it. (The side rail does not have this problem: there both are in
   * ONE row, whose height comes from the panel.) */
  .dashboard-buddy-tabs {
    display: block;
  }

  .dashboard-buddy-tabs__panels {
    margin-top: var(--db-gap);
  }

  .dashboard-buddy-tabs__nav {
    max-height: none;
    /* Nothing scrolls and nothing is clipped — the band is as tall as its
     * lines. The left inset the side rail needs for its marker is dropped
     * with it: here the marker is an underline, inside the item. */
    overflow: visible;
    /* Full-bleed, so the band's own edges are the screen's. */
    margin: 0 calc(var(--db-gap) * -1);
    padding: var(--db-gap-sm) var(--db-gap);
    background: var(--db-bg-content);
    z-index: 3;
  }

  .dashboard-buddy-tabs__list {
    flex-direction: row;
    flex-wrap: wrap;
    /* Centred, which matters most on the LAST line: a wrapped band nearly
     * always ends short, and left-aligning leaves a ragged gap that reads
     * as missing items rather than the end of the list. */
    justify-content: center;
    /* Rows closer together than items are apart: it reads as one band of
     * links rather than a grid of cells. */
    gap: var(--db-gap-sm) var(--db-gap-lg);
    width: auto;
  }

  .dashboard-buddy-tabs__link {
    width: auto;
    padding-bottom: 4px;
    /* A label never breaks mid-word — an item wraps whole or not at all. */
    white-space: nowrap;
  }

  /* The open marker turns with the rail: a bar under the item, not beside. */
  .dashboard-buddy-tabs__link[aria-current="true"]::before {
    top: auto;
    right: 0;
    bottom: 0;
    left: 0;
    width: auto;
    height: 3px;
  }
}

/* Phone. Nine labelled items on a 390px screen is three or four lines at
 * the desktop scale, and a sticky band that deep costs more than the rail
 * is worth. Smaller type, smaller icons, tighter gaps — same items, fewer
 * lines. */
@media screen and (max-width: 768px) {
  .dashboard-buddy-tabs__list {
    gap: var(--db-gap-sm) var(--db-gap);
  }

  .dashboard-buddy-tabs__link {
    gap: var(--db-gap-sm);
    font-size: 16px;
  }

  .dashboard-buddy-tabs__icon {
    flex-basis: 16px;
    width: 16px;
    height: 16px;
    font-size: var(--db-text-md);
    line-height: 16px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .dashboard-buddy-tabs__link {
    transition: none;
  }
}

/* ---------------------------------------------------------------------------
 * RESOURCES
 *
 * Wide nav cards, two across: round icon badge, then a title and a longer
 * description beside it, chevron at the right edge.
 *
 * Deliberately NOT the Discover tiles restyled. Those stack a small icon
 * above their text in an auto-fitting row of narrow tiles, because three had
 * to fit the dashboard column; these are half-width cards carrying two lines
 * of copy. Sharing one rule set would have meant a layout switch threaded
 * through every declaration, and Discover is still on the page until the
 * swap is made.
 * ------------------------------------------------------------------------ */

.dashboard-buddy__resources {
  display: grid;
  grid-template-columns: repeat(var(--db-resource-cols, 2), minmax(0, 1fr));
  gap: var(--db-gap);
}

.dashboard-buddy__resource {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--db-gap);
  /* Right padding clears the chevron; the extra above and below is what
   * gives these cards their height next to Discover's tighter tiles. */
  padding: var(--db-gap-lg) 40px var(--db-gap-lg) var(--db-gap-lg);
  width: 100%;
  background: var(--db-surface);
  border: var(--db-card-border-width) solid var(--db-card-border-color);
  border-radius: var(--db-radius);
  font-family: inherit;
  text-align: left;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  transition: background-color 0.2s ease, border-color 0.2s ease;
}

.dashboard-buddy__resource:hover,
.dashboard-buddy__resource:focus-visible {
  background: var(--db-surface-raised);
  border-color: var(--db-accent-hover);
  text-decoration: none;
}

/* The badge: the accent at low strength, so it reads as a tint of the brand
 * red rather than a seventh red in a palette that already documents nine.
 * The flat rgba is the fallback for anything without color-mix. */
.dashboard-buddy__resource-badge {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: rgba(226, 59, 59, 0.16);
  background: color-mix(in srgb, var(--db-accent) 16%, transparent);
}

/* Masked, so the glyph takes the badge's colour — see Dashboard_Buddy_Icons
 * for why an icon font is not an option on this site. */
.dashboard-buddy__resource-icon {
  width: 24px;
  height: 24px;
  background-color: var(--db-text);
  -webkit-mask: var(--db-icon) no-repeat center / contain;
  mask: var(--db-icon) no-repeat center / contain;
}

.dashboard-buddy__resource-body {
  min-width: 0;
}

.dashboard-buddy__resource-title {
  display: block;
  margin: 0 0 4px;
  font-family: var(--db-font-heading);
  font-size: var(--db-card-title-size);
  line-height: 1.1;
  color: var(--db-text);
}

.dashboard-buddy__resource-text {
  display: block;
  font-size: var(--db-text-lg);
  line-height: 1.35;
  color: var(--db-text-muted);
}

/* Centred on the card rather than parked in the corner the way Discover's
 * is — these are wide and short, so the corner reads as unattached. */
.dashboard-buddy__resource .dashboard-buddy__tile-arrow {
  top: 50%;
  right: var(--db-gap-lg);
  margin-top: -5px;
}

@media screen and (max-width: 768px) {
  .dashboard-buddy__resources {
    grid-template-columns: minmax(0, 1fr);
  }

  .dashboard-buddy__resource {
    gap: var(--db-gap-sm);
    padding: var(--db-gap) 34px var(--db-gap) var(--db-gap);
  }

  .dashboard-buddy__resource-badge {
    width: 44px;
    height: 44px;
  }

  .dashboard-buddy__resource-icon {
    width: 20px;
    height: 20px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .dashboard-buddy__resource {
    transition: none;
  }
}

/* ---------------------------------------------------------------------------
 * DAILY SHORTS
 *
 * Built to sit alongside the hand-assembled rows on the Originals page, so
 * the measurements below are taken off those rather than invented: heading
 * 14px/700 with 0.4em tracking over a hairline rule, thumbnails at a 6px
 * radius, 25px between rows, caption 16px directly beneath.
 * ------------------------------------------------------------------------ */

/* The section heading, in the Originals treatment rather than this plugin's
 * usual condensed one — on that page it has to match its neighbours, and it
 * is the only element here that lives outside the dashboard. */
.dashboard-buddy--db_daily_shorts .dashboard-buddy__heading {
  font-family: var(--db-font-body);
  font-size: var(--db-text-md);
  font-weight: 700;
  letter-spacing: 0.4em;
  padding-bottom: var(--db-gap);
  border-bottom: 1px solid rgba(201, 201, 201, 0.2);
}

.dashboard-buddy--db_daily_shorts .dashboard-buddy__header {
  display: block;
  margin-bottom: var(--db-gap-lg);
}

.dashboard-buddy__shorts {
  display: grid;
  /* auto-fill would leave a lone short at a quarter width here. This grid is
   * an explicit column count, not a fitted one, so a single short takes a
   * proper half of the row the way the rows above it do. */
  grid-template-columns: repeat(var(--db-shorts-cols, 2), minmax(0, 1fr));
  gap: 25px var(--db-gap-lg);
}

.dashboard-buddy__short {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font-family: inherit;
  text-align: left;
  color: inherit;
  cursor: pointer;
}

.dashboard-buddy__short-thumb {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: 6px;
  background: var(--db-placeholder-bg);
}

.dashboard-buddy__short-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Same branded mark the cards use when there is no frame to show. */
.dashboard-buddy__short-thumb.is-placeholder::before {
  content: "";
  position: absolute;
  inset: 0;
  background-color: var(--db-placeholder-mark);
  -webkit-mask: url("../img/dc-mark.svg") no-repeat center / 34%;
  mask: url("../img/dc-mark.svg") no-repeat center / 34%;
  opacity: 0.5;
}

.dashboard-buddy__short:hover .dashboard-buddy__short-thumb img,
.dashboard-buddy__short:focus-visible .dashboard-buddy__short-thumb img {
  opacity: 0.85;
}

.dashboard-buddy__short-title {
  display: block;
  margin-top: var(--db-gap-sm);
  font-size: 16px;
  line-height: 1.3;
  color: var(--db-text);
}

.dashboard-buddy__short-date {
  display: block;
  margin-top: 2px;
  font-size: var(--db-text-md);
  color: var(--db-text-muted);
}

@media screen and (max-width: 768px) {
  .dashboard-buddy__shorts {
    grid-template-columns: minmax(0, 1fr);
  }
}
