/*
 * Structural styles only. Colour comes from props, so the component drops into
 * any design without a token layer to override. The few colours here belong to
 * the tooltip, which needs contrast against the page rather than the data.
 */
.heatmap {
  --heatmap-tooltip-bg: rgba(26, 24, 21, 0.97);
  --heatmap-tooltip-text: #ffffff;
  --heatmap-tooltip-border: rgba(255, 255, 255, 0.12);
  --heatmap-tooltip-shadow: 0 12px 28px rgba(26, 24, 21, 0.22);
  --heatmap-label: #57606a;
  /* Placeholders sit back from the data; the current ring stands out from it.
   * Both follow the page's own text colour, so neither assumes a background. */
  --heatmap-placeholder-color: color-mix(in srgb, currentColor 10%, transparent);
  --heatmap-placeholder-fill: color-mix(in srgb, currentColor 2%, transparent);
  --heatmap-current-color: currentColor;
  --heatmap-current-width: 2px;

  display: inline-flex;
  flex-direction: column;
  gap: 16px;
  font-family: inherit;
}

.heatmap *,
.heatmap *::before,
.heatmap *::after {
  box-sizing: border-box;
}

/* Scrolling keeps the chart inside its container instead of spilling out. */
.heatmap[data-overflow="scroll"] {
  max-width: 100%;
}

/* A fitted chart spans its container, so the space it measures is the
 * container's and never its own content's. */
.heatmap[data-fit] {
  display: flex;
  width: 100%;
}

/*
 * Row labels sit beside the viewport rather than inside it, so they stay put
 * while the cells scroll. Being outside, they need no background to hide the
 * cells passing beneath, which a sticky label would.
 */
.heatmap__body {
  display: flex;
  align-items: flex-start;
  min-width: 0;
}

/*
 * The viewport scrolls; the canvas inside it holds the column labels and the
 * cells together, so they cannot drift out of alignment. Padding cancelled by
 * negative margins gives focus rings and hovered cells room inside the clip
 * without moving the grid. The end side gets padding only while scrolling,
 * since a negative margin there would hang past the container.
 */
.heatmap__viewport {
  flex: 0 1 auto;
  min-width: 0;
  margin: -4px 0 -4px -4px;
  padding: 4px 0 4px 4px;
}

.heatmap[data-fit] .heatmap__viewport {
  flex: 1 1 0;
}

.heatmap__viewport[data-scrolling] {
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  padding-right: 4px;
}

/* Scroll origin follows direction, so an end-anchored viewport opens on its
 * newest column and stays there as it resizes, with or without script. */
.heatmap__viewport[data-anchor="end"] {
  direction: rtl;
}

.heatmap__viewport:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: -2px;
  border-radius: 4px;
}

.heatmap__canvas {
  direction: ltr;
  width: max-content;
  /* Keeps a narrower grid at the start edge inside a right-to-left viewport. */
  margin-right: auto;
}

.heatmap__grid {
  position: relative;
}

.heatmap__cell-slot {
  position: absolute;
  display: grid;
  place-items: center;
}

.heatmap__cell-slot[tabindex] {
  cursor: default;
}

.heatmap__cell-slot[role="button"] {
  cursor: pointer;
}

.heatmap__cell-slot:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
  border-radius: 3px;
}

.heatmap__cell {
  position: relative;
  display: grid;
  place-items: center;
  overflow: hidden;
  transition: transform 140ms ease-out, opacity 140ms ease-out;
}

.heatmap__cell-content {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: grid;
  place-items: center;
  min-width: 0;
  padding: 1px;
  overflow: hidden;
  font-weight: 500;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  text-align: center;
  white-space: nowrap;
  color: var(--heatmap-cell-content-color, currentColor);
  pointer-events: none;
  text-shadow: 0 1px 1px rgb(0 0 0 / 0.18);
}

.heatmap__cell-slot:hover .heatmap__cell,
.heatmap__cell-slot:focus-visible .heatmap__cell {
  transform: scale(1.18);
}

/*
 * Outlines. Shapes drawn with a radius take a CSS border or outline; clipped
 * shapes (diamond, hexagon, plus) would lose it to their own clip path, so they
 * get an SVG polygon instead and the CSS line is switched off.
 */
.heatmap__outline {
  position: absolute;
  inset: 0;
  z-index: 2;
  overflow: visible;
  pointer-events: none;
}

.heatmap__outline polygon {
  fill: none;
}

/* A placeholder is the cell's shape, barely there: a light dashed line over a
 * tint faint enough that it never passes for an empty cell. */
.heatmap__placeholder {
  position: relative;
  overflow: hidden;
  border: 1px dashed var(--heatmap-placeholder-color);
  background: var(--heatmap-placeholder-fill);
}

.heatmap__placeholder-outline polygon {
  stroke: var(--heatmap-placeholder-color);
  /* Half of this is clipped away, leaving a 1px line inside the shape. */
  stroke-width: 2;
  stroke-dasharray: 3 2;
}

/* The ring sits inside the cell's edge, so it never collides with a neighbour
 * or with the focus ring drawn outside the slot. */
.heatmap__cell-slot[data-current] .heatmap__cell {
  outline: var(--heatmap-current-width) solid var(--heatmap-current-color);
  outline-offset: calc(-1 * var(--heatmap-current-width));
}

.heatmap__current-ring polygon {
  stroke: var(--heatmap-current-color);
  stroke-width: calc(2 * var(--heatmap-current-width));
}

.heatmap:is([data-shape="diamond"], [data-shape="hexagon"], [data-shape="plus"]) .heatmap__placeholder {
  border: 0;
}

.heatmap:is([data-shape="diamond"], [data-shape="hexagon"], [data-shape="plus"])
  .heatmap__cell-slot[data-current] .heatmap__cell {
  outline: 0;
}

/*
 * Depth makes each cell a raised pad, lit from above. Four shadows do it: a
 * darker lip along the bottom edge, a crisp highlight on the top rim, a soft
 * light falling inside the top, and a shadow beneath that lifts the pad off
 * the page. A sheen over the face brightens it toward the top. Every layer is
 * a shadow or an overlay, so the cell's size and the grid's layout are
 * untouched, and every distance scales with --heatmap-depth-size.
 *
 * A ring's inside is its hole, where a lip or a sheen would read as a smudge,
 * so rings stay flat.
 */
.heatmap[data-depth] {
  --heatmap-depth-lip: rgb(0 0 0 / 0.32);
  --heatmap-depth-highlight: rgb(255 255 255 / 0.2);
  --heatmap-depth-glow: rgb(255 255 255 / 0.07);
  --heatmap-depth-shadow: rgb(0 0 0 / 0.3);
}

.heatmap[data-depth] .heatmap__cell {
  --d: var(--heatmap-depth-size, 2px);
  box-shadow: var(
    --heatmap-cell-depth,
    inset 0 calc(-1 * var(--d)) 0 var(--heatmap-depth-lip),
    inset 0 1px 0 var(--heatmap-depth-highlight),
    inset 0 calc(1.5 * var(--d)) calc(1.5 * var(--d)) calc(-1 * var(--d)) var(--heatmap-depth-glow),
    0 calc(0.75 * var(--d)) calc(1.5 * var(--d)) var(--heatmap-depth-shadow)
  );
}

.heatmap[data-depth] .heatmap__cell::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(
    --heatmap-cell-sheen,
    linear-gradient(to bottom, rgb(255 255 255 / 0.06), rgb(255 255 255 / 0) 45%)
  );
  pointer-events: none;
}

/* The shadow beneath reaches past the 4px a scrolling viewport leaves for focus
 * rings, so the bottom row gets room for it, cancelled the same way. */
.heatmap[data-depth] .heatmap__viewport {
  margin-bottom: calc(-4px - 2.25 * var(--heatmap-depth-size, 2px));
  padding-bottom: calc(4px + 2.25 * var(--heatmap-depth-size, 2px));
}

.heatmap[data-depth][data-shape="ring"] .heatmap__cell {
  box-shadow: none;
}

.heatmap[data-depth][data-shape="ring"] .heatmap__cell::before {
  content: none;
}

/* Column labels sit above the cells, each centred on the column it names. */
.heatmap__column-labels {
  position: relative;
}

.heatmap__column-label {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  color: var(--heatmap-label);
  font-size: 10px;
  line-height: 1;
  white-space: nowrap;
}

.heatmap__row-labels {
  flex: none;
  /* Size the track to its widest label; the gutter is a margin, not a gap. */
  margin-inline-end: 6px;
  position: relative;
  display: grid;
  align-content: start;
  width: max-content;
}

.heatmap__row-label {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  color: var(--heatmap-label);
  font-size: 10px;
  line-height: 1;
  white-space: nowrap;
}

.heatmap__legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 3px;
  color: var(--heatmap-label);
  font-size: 10px;
}

.heatmap__legend span:first-child {
  margin-right: 2px;
}

.heatmap__legend-unknown {
  margin-left: 0.5rem;
}

.heatmap__legend-swatch {
  display: inline-block;
  flex: none;
}

.heatmap__tooltip {
  position: absolute;
  bottom: calc(100% + 8px);
  z-index: 20;
  width: max-content;
  max-width: 220px;
  padding: 6px 9px;
  border: 1px solid var(--heatmap-tooltip-border);
  border-radius: 8px;
  background: var(--heatmap-tooltip-bg);
  box-shadow: var(--heatmap-tooltip-shadow);
  color: var(--heatmap-tooltip-text);
  font-size: 11px;
  line-height: 1.45;
  pointer-events: none;
}

.heatmap__tooltip[data-align="center"] {
  left: 50%;
  transform: translateX(-50%);
}

.heatmap__tooltip[data-align="start"] {
  left: 0;
}

.heatmap__tooltip[data-align="end"] {
  right: 0;
}

/* In the top layer the tooltip is placed by script in viewport coordinates.
 * These undo the absolute placement above and the UA popover defaults. */
.heatmap__tooltip[popover] {
  position: fixed;
  inset: auto;
  margin: 0;
  overflow: visible;
}

@media (prefers-color-scheme: dark) {
  .heatmap:not([data-heatmap-theme="light"]) {
    --heatmap-tooltip-bg: rgba(240, 238, 235, 0.97);
    --heatmap-tooltip-text: #171614;
    --heatmap-tooltip-border: rgba(0, 0, 0, 0.14);
    --heatmap-label: #9a938a;
  }
}

.heatmap[data-heatmap-theme="dark"] {
  --heatmap-tooltip-bg: rgba(240, 238, 235, 0.97);
  --heatmap-tooltip-text: #171614;
  --heatmap-tooltip-border: rgba(0, 0, 0, 0.14);
  --heatmap-label: #9a938a;
}

@media (prefers-reduced-motion: reduce) {
  .heatmap__cell {
    transition: none;
  }
  .heatmap__cell-slot:hover .heatmap__cell,
  .heatmap__cell-slot:focus-visible .heatmap__cell {
    transform: none;
  }
}

/* Three-dimensional charts share the colour ramp and tooltip tokens with the
 * flat grid. Neutral architecture lets the data provide the colour. */
.heatmap3d {
  --heatmap3d-floor: #f0f1f3;
  --heatmap3d-floor-edge: #d9dde2;
  --heatmap3d-grid: #d9dde2;
  --heatmap3d-ink: #7b838e;
  --heatmap3d-window: #f7fff5;
  position: relative;
  display: flex;
  width: 100%;
  min-width: 0;
}
.heatmap3d__scene {
  display: block;
  width: 100%;
  height: clamp(280px, 40vw, 440px);
  overflow: hidden;
  user-select: none;
  -webkit-user-select: none;
}
.heatmap3d__scene[data-interactive="true"] { cursor: grab; touch-action: none; }
.heatmap3d__scene[data-dragging="true"] { cursor: grabbing; }
.heatmap3d__scene:focus-visible { outline: 2px solid var(--heatmap3d-ink); outline-offset: -3px; border-radius: 12px; }
.heatmap3d__floor-edge { fill: var(--heatmap3d-floor-edge); }
.heatmap3d__floor-surface { fill: var(--heatmap3d-floor); stroke: var(--heatmap3d-floor-edge); stroke-width: 0.7; }
.heatmap3d__grid-line { fill: none; stroke: var(--heatmap3d-grid); stroke-width: 0.45; opacity: 0.7; }
.heatmap3d__contact-shadow { fill: #111b2b; opacity: 0.13; }
.heatmap3d__surface { stroke: #ffffff30; stroke-width: 0.3; stroke-linejoin: round; }
.heatmap3d[data-shape="circle"] .heatmap3d__surface[data-face="left"],
.heatmap3d[data-shape="circle"] .heatmap3d__surface[data-face="right"] { stroke: none; }
.heatmap3d__window { fill: var(--heatmap3d-window); }
.heatmap3d__roof { fill: #07101b; fill-opacity: 0.11; stroke: #ffffff70; stroke-width: 0.45; stroke-linejoin: round; pointer-events: none; }
.heatmap3d__unknown { stroke: var(--heatmap3d-ink); stroke-width: 0.7; stroke-dasharray: 2 1.8; pointer-events: all; }
.heatmap3d__cell { outline: none; }
.heatmap3d__cell[role="button"] { cursor: pointer; }
.heatmap3d__cell[data-active="true"] .heatmap3d__surface,
.heatmap3d__cell:focus-visible .heatmap3d__surface { stroke: #fff; stroke-width: 1.15; }
.heatmap3d__cell[data-active="true"] .heatmap3d__unknown,
.heatmap3d__cell:focus-visible .heatmap3d__unknown { stroke: currentColor; stroke-width: 1.5; stroke-dasharray: none; }
.heatmap3d__cell:focus-visible { filter: drop-shadow(0 0 1px var(--heatmap3d-ink)); }
.heatmap3d__axis line { stroke: var(--heatmap3d-ink); stroke-width: 0.6; opacity: 0.65; }
.heatmap3d__axis text, .heatmap3d__labels text { fill: var(--heatmap3d-ink); font-family: inherit; font-size: 8px; }
.heatmap3d__labels, .heatmap3d__axis { pointer-events: none; }
.heatmap3d .heatmap3d__tooltip {
  display: flex;
  flex-direction: column;
  gap: 3px;
  bottom: auto;
  right: auto;
  padding: 9px 12px;
  border-radius: 9px;
  transform: translate(-50%, calc(-100% - 14px));
}
.heatmap3d .heatmap3d__tooltip[data-align="start"] { transform: translate(0, calc(-100% - 14px)); }
.heatmap3d .heatmap3d__tooltip[data-align="end"] { transform: translate(-100%, calc(-100% - 14px)); }
.heatmap3d__tooltip strong { font-size: 17px; font-variant-numeric: tabular-nums; line-height: 1.2; }
.heatmap3d__tooltip-label { opacity: 0.7; font-size: 10px; }
.heatmap3d__controls {
  position: absolute;
  right: 8px;
  bottom: 8px;
  display: flex;
  gap: 2px;
  padding: 3px;
  border: 1px solid color-mix(in srgb, var(--heatmap3d-ink) 18%, transparent);
  border-radius: 9px;
  background: color-mix(in srgb, var(--heatmap3d-floor) 90%, transparent);
  backdrop-filter: blur(8px);
}
.heatmap3d__controls button {
  display: grid;
  place-items: center;
  min-width: 32px;
  height: 32px;
  padding: 0 7px;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: var(--heatmap3d-ink);
  font-family: inherit;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}
.heatmap3d__controls button:hover { background: color-mix(in srgb, var(--heatmap3d-ink) 10%, transparent); }
.heatmap3d__controls button:disabled { opacity: 0.35; cursor: default; }
.heatmap3d__controls button:focus-visible { outline: 2px solid currentColor; outline-offset: 1px; }
.heatmap3d__reset svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.35; stroke-linecap: round; stroke-linejoin: round; }
.heatmap3d__legend { justify-content: center; padding: 4px 0 10px; }
.heatmap3d__legend .heatmap__legend-swatch { width: 9px; height: 9px; border-radius: 2px; }
.heatmap3d__legend-unknown-swatch { width: 9px; height: 9px; border: 1px dashed var(--heatmap3d-ink); border-radius: 2px; }
.heatmap3d__static-legend text { fill: var(--heatmap3d-ink); font-family: inherit; font-size: 8px; }
.heatmap3d__sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.heatmap3d[data-heatmap-theme="dark"] {
  --heatmap3d-floor: #24272d;
  --heatmap3d-floor-edge: #353a43;
  --heatmap3d-grid: #3d424b;
  --heatmap3d-ink: #a6afbc;
  --heatmap3d-window: #ffd786;
}
@media (prefers-color-scheme: dark) {
  .heatmap3d:not([data-heatmap-theme="light"]) {
    --heatmap3d-floor: #24272d;
    --heatmap3d-floor-edge: #353a43;
    --heatmap3d-grid: #3d424b;
    --heatmap3d-ink: #a6afbc;
    --heatmap3d-window: #ffd786;
  }
}
@media (pointer: coarse) {
  .heatmap3d__controls button { min-width: 44px; height: 44px; }
}
@media (max-width: 540px) {
  .heatmap3d__controls {
    position: relative;
    right: auto;
    bottom: auto;
    align-self: center;
    margin: 0 0 12px;
  }
  .heatmap3d__legend { flex-wrap: wrap; }
}

/* A pattern is an overlay, so the resolved face colour remains visible below
 * sparse bitmap marks. This also keeps custom bitmap patterns colour-agnostic. */
.heatmap3d__surface-base { stroke: none; }

/* Grow is an entrance explanation: the grid settles into its measured
 * elevations in painter order. It is opt-in and uses transform/opacity only,
 * so changing a value never causes the chart to jump in layout. */
@keyframes heatmap3d-grow {
  from { opacity: 0; transform: translateY(4px) scale(0.97); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes heatmap3d-reduced-fade {
  from { opacity: 0.45; }
  to { opacity: 1; }
}

.heatmap3d[data-animation="grow"] .heatmap3d__cell[data-known="true"],
.heatmap3d__scene[data-animation="grow"] .heatmap3d__cell[data-known="true"] {
  animation: heatmap3d-grow 240ms cubic-bezier(0.23, 1, 0.32, 1) both;
  animation-delay: calc(min(var(--heatmap3d-index, 0), 32) * 26ms);
  transform-box: fill-box;
  transform-origin: center bottom;
}

/* Reduced motion preserves the arrival cue while removing spatial travel and
 * all stagger. */
@media (prefers-reduced-motion: reduce) {
  .heatmap3d[data-animation="grow"] .heatmap3d__cell[data-known="true"],
  .heatmap3d__scene[data-animation="grow"] .heatmap3d__cell[data-known="true"] {
    animation: heatmap3d-reduced-fade 160ms ease-out both;
    animation-delay: 0ms;
    transform: none;
  }
}

/* The night preset owns its architectural neutrals even when the page itself
 * is in light mode. */
.heatmap3d[data-heatmap3d-theme="night"],
.heatmap3d__scene[data-heatmap3d-theme="night"] {
  --heatmap3d-floor: #24272d;
  --heatmap3d-floor-edge: #353a43;
  --heatmap3d-grid: #3d424b;
  --heatmap3d-ink: #a6afbc;
  --heatmap3d-window: #ffd786;
}

.heatmap3d[data-heatmap3d-theme="seasonal"],
.heatmap3d__scene[data-heatmap3d-theme="seasonal"] {
  --heatmap3d-window: #fffaf0;
}

.heatmap3d[data-heatmap3d-theme="rainbow"],
.heatmap3d__scene[data-heatmap3d-theme="rainbow"] {
  --heatmap3d-ink: #687080;
  --heatmap3d-window: #fff8dd;
}
