/**
 * cue.css - The minimum styling the framework needs to function.
 *
 * Deliberately unopinionated about appearance: this file only contains rules
 * that make effects *work* (pinning geometry, header transitions, submenu
 * visibility). Colours, type, and spacing belong to the site, not here.
 */

/* -------------------------------------------------- pre-paint hiding */

/*
 * Effects that animate content *in* need it hidden before first paint, or
 * you get a flash of the un-animated state. But hiding it from CSS is a
 * liability: if the module fails to load — network error, syntax error, an
 * older browser — the content stays hidden forever.
 *
 * So the hiding is tied to `.cue-init`, which the page adds inline and which
 * gets removed two ways: by cue once effects have painted, and by a failsafe
 * timeout if cue never starts. A visible unanimated page always beats an
 * invisible one.
 *
 *   <script>
 *     var d = document.documentElement;
 *     d.classList.add('cue-init');
 *     setTimeout(function () { d.classList.remove('cue-init'); }, 2000);
 *   </script>
 */
.cue-init [data-cue~="reveal"] { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .cue-init [data-cue~="reveal"] { opacity: 1; }
}

/* ------------------------------------------------------------------- pin */

/*
 * The track reserves the scroll distance permanently, so pinning never
 * changes document height mid-scroll. The target sticks within it — the
 * browser keeps it in place on the compositor, so there's no JS jitter.
 */
.cue-pin-track {
  position: relative;
  height: var(--cue-pin-height, auto);
}

/*
 * Doubled class, deliberately. Container effects give their element a
 * `position` — .cue-map and .cue-slides both want `relative` — and at equal
 * specificity whichever rule appears later in this file wins. When that isn't
 * pin, the section silently stops sticking and scrolls away mid-animation,
 * which looks like the effect is broken rather than the pinning.
 *
 * Sticky must always win here, so give it (0,2,0) and stop worrying about
 * source order every time a new container effect is added.
 */
.cue-pin-target.cue-pin-target {
  position: sticky;
  top: var(--cue-pin-top, 0px);
}

/* Without JS the track has no height and the target just sits in flow. */
.no-js .cue-pin-track { height: auto; }
.no-js .cue-pin-target { position: static; }

/* ---------------------------------------------------------------- header */

/*
 * Raise --cue-header-z if a pinned section ever paints over the nav. 100 sits
 * above ordinary content; anything creating its own stacking context at a
 * higher value — a third-party widget, say — needs this raised to match.
 */
.cue-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: var(--cue-header-z, 100);
  transition:
    transform 320ms cubic-bezier(0.16, 1, 0.3, 1),
    opacity 220ms ease,
    background-color 220ms ease;
  will-change: transform;
}

.cue-header.is-hidden {
  transform: translateY(-100%);
  opacity: 0;
  pointer-events: none;
}

.cue-header.is-visible {
  transform: translateY(0);
  opacity: 1;
}

/*
 * Peek: collapsed to a sliver rather than gone. Opacity and pointer-events
 * stay on, so there's always something to aim the pointer at — that strip is
 * the whole affordance.
 */
.cue-header.is-peek {
  transform: translateY(calc(-100% + var(--cue-peek-height, 8px)));
  opacity: 1;
  pointer-events: auto;
}

/* A hairline so the collapsed strip is visibly a thing, not a rendering slip. */
.cue-header.is-peek::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: var(--cue-peek-height, 8px);
  background: var(--cue-peek-hint, currentColor);
  opacity: 0.5;
}

/* ---------------------------------------------------------------- submenu */

.cue-has-submenu {
  position: relative;
}

.cue-submenu {
  position: absolute;
  top: 100%;
  left: 0;
  min-width: max(100%, 12rem);
  margin: 0;
  padding: 0.4rem 0;
  list-style: none;

  opacity: 0;
  visibility: hidden;
  transform: translateY(-0.4rem);
  transition:
    opacity 180ms ease,
    transform 180ms ease,
    visibility 0s linear 180ms;
}

.cue-has-submenu.is-open > .cue-submenu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition-delay: 0s;
}

/* ---------------------------------------------------------------- drawer */

/*
 * All drawer styling hangs off classes the effect toggles — never a media
 * query here, because the breakpoint is a JS option and the stylesheet can't
 * know it. Above the breakpoint none of these classes exist and the nav is
 * untouched.
 */
/* An author 'display' beats the UA's [hidden] rule — restate it, or the
   toggle shows on desktop. */
.cue-drawer-toggle[hidden] { display: none; }

.cue-drawer-toggle {
  display: inline-grid;
  place-items: center;
  width: 2.6rem;
  height: 2.6rem;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
  z-index: calc(var(--cue-header-z, 100) + 2);
}

.cue-burger { display: grid; gap: 5px; width: 1.25rem; }
.cue-burger i {
  height: 2px;
  background: currentColor;
  border-radius: 2px;
  transition: transform 220ms ease, opacity 160ms ease;
}

/* Burger folds into a cross while open. */
.cue-drawer-toggle.is-open .cue-burger i:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.cue-drawer-toggle.is-open .cue-burger i:nth-child(2) { opacity: 0; }
.cue-drawer-toggle.is-open .cue-burger i:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* The nav as an off-canvas panel. Site CSS supplies its background. */
.cue-drawer-mode {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(22rem, 86vw);
  transform: translateX(100%);
  visibility: hidden;
  transition: transform 280ms cubic-bezier(0.16, 1, 0.3, 1), visibility 0s linear 280ms;
  overflow-y: auto;
  z-index: calc(var(--cue-header-z, 100) + 1);
}

.cue-drawer-mode.is-open {
  transform: translateX(0);
  visibility: visible;
  transition-delay: 0s;
}

.cue-drawer-scrim {
  position: fixed;
  inset: 0;
  background: rgb(0 0 0 / 0.5);
  opacity: 0;
  visibility: hidden;
  transition: opacity 240ms ease, visibility 0s linear 240ms;
  z-index: var(--cue-header-z, 100);
}

.cue-drawer-scrim.is-open {
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}

/* The page behind an open drawer holds still. */
.cue-drawer-locked { overflow: hidden; }

/*
 * While the drawer is open, the header must ride above the panel and scrim,
 * or its toggle — the close button — is covered by the very panel it opened.
 * (If your header doesn't use cue's `header` effect, raise its z-index under
 * `.cue-drawer-locked` yourself.)
 */
.cue-drawer-locked .cue-header {
  z-index: calc(var(--cue-header-z, 100) + 2);
}

/* Inside the drawer, horizontal menus stack... */
.cue-drawer-mode > ul {
  display: block;
}

/* ...and submenus — mega included — become in-flow lists instead of
   floating panels. Doubled class to outrank the mega positioning rules. */
.cue-drawer-mode .cue-submenu.cue-submenu {
  position: static;
  display: block;
  min-width: 0;
  transform: none;
  box-shadow: none;
}

/* Closed submenus in the drawer take no space. */
.cue-drawer-mode .cue-has-submenu:not(.is-open) > .cue-submenu {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .cue-drawer-mode,
  .cue-drawer-scrim,
  .cue-burger i {
    transition-duration: 0.01ms !important;
  }
}

/* ------------------------------------------------------------ mega panel */

/*
 * `position: static` is the whole trick: it takes the item out of the
 * containing-block chain, so the panel's absolute positioning resolves
 * against the header instead — letting it span the full width.
 *
 * Doubled class deliberately. Sites very commonly carry a `nav li {
 * position: relative }` rule, and at (0,1,1) that would outrank a single
 * class and silently collapse the panel to the width of its menu item.
 */
.cue-has-submenu.cue-has-submenu--mega { position: static; }

/*
 * Sizing is driven by custom properties so a site can retune the panel
 * without having to out-specify these rules. Set them on the panel, on the
 * nav, or on :root — whatever scope suits.
 *
 *   --cue-menu-card    minimum card width before the grid rewraps  (11rem)
 *   --cue-menu-gap     space between cards                       (0.75rem)
 *   --cue-menu-pad     panel inner padding                          (1rem)
 *   --cue-menu-ratio   aspect ratio of each card's image           (16/10)
 */
.cue-submenu--mega {
  left: 0;
  right: 0;
  min-width: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--cue-menu-card, 11rem), 1fr));
  gap: var(--cue-menu-gap, 0.75rem);
  padding: var(--cue-menu-pad, 1rem);
}

/* Give one item more room — a featured service, say. */
.cue-submenu--mega > li[data-cue-span="2"] { grid-column: span 2; }
.cue-submenu--mega > li[data-cue-span="3"] { grid-column: span 3; }

/*
 * A spanned card is wider, so at a fixed ratio its image would also be
 * proportionally taller — forcing the whole grid row to that height and
 * leaving its neighbours floating in dead space. Widening the ratio in step
 * with the span keeps every card the same height, and gives the featured
 * item a letterbox image rather than an outsized one.
 */
.cue-submenu--mega > li[data-cue-span="2"] :is(figure, .cue-menu-media) {
  aspect-ratio: var(--cue-menu-ratio-2, 32/10);
}

.cue-submenu--mega > li[data-cue-span="3"] :is(figure, .cue-menu-media) {
  aspect-ratio: var(--cue-menu-ratio-3, 48/10);
}

/*
 * Note: a spanned item occupies two of the grid's auto-fit slots, so the
 * column count has to accommodate it — five items with one doubled needs
 * six slots. If it doesn't fit, the last card wraps and leaves a visible
 * hole. Tune --cue-menu-card until the arithmetic works at your breakpoints.
 */

/* Images in menu items shouldn't need per-site rules just to behave. */
.cue-submenu img {
  display: block;
  width: 100%;
  height: auto;
}

/*
 * A wrapper element around the image gets a fixed ratio, so cards stay the
 * same height whatever the source image dimensions are — which matters when
 * the media library is a mix of 2560px photos and 400px screenshots.
 */
.cue-submenu :is(figure, .cue-menu-media) {
  margin: 0;
  overflow: hidden;
  aspect-ratio: var(--cue-menu-ratio, 16/10);
}

.cue-submenu :is(figure, .cue-menu-media) img {
  height: 100%;
  object-fit: cover;
}

.cue-submenu-toggle {
  display: inline-grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
}

.cue-caret {
  width: 0.45rem;
  height: 0.45rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: translateY(-15%) rotate(45deg);
  transition: transform 200ms ease;
}

.cue-has-submenu.is-open .cue-caret {
  transform: translateY(15%) rotate(-135deg);
}

/* Submenus need a visible focus ring even against a dark header. */
.cue-submenu-toggle:focus-visible,
.cue-submenu a:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

/* -------------------------------------------------------------- map */

/*
 * The element is the viewport onto the map. Give it a height in your own
 * stylesheet — a map with no height is the most common way this goes wrong.
 */
.cue-map {
  overflow: hidden;
}

/*
 * The canvas is positioned against this element, which needs a containing
 * block — but NOT at the cost of clobbering `position: sticky` when `map`
 * and `pin` share an element (the usual arrangement). Sticky establishes a
 * containing block too, so it already does the job.
 */
.cue-map:not(.cue-pin-target) {
  position: relative;
}

/*
 * The explicit z-index matters more than it looks. With `z-index: auto` an
 * absolutely positioned element creates no stacking context, so Leaflet's
 * internal panes — which sit at z-index 400-700 — would escape and compete
 * with this element's siblings, painting straight over any overlay content.
 * A real z-index contains them.
 */
.cue-map-canvas {
  position: absolute;
  inset: 0;
  z-index: 1;
}

/* While non-interactive the map is decoration, so don't invite clicks. */
.cue-map .cue-map-canvas { pointer-events: none; }
.cue-map.is-interactive .cue-map-canvas { pointer-events: auto; }

/*
 * Author-supplied fallback image sits underneath, visible until tiles paint.
 *
 * Absolutely positioned, because it's a background layer rather than content.
 * Left in normal flow it would consume the section's full height and push any
 * overlay card below the fold, where `overflow: hidden` silently eats it.
 */
.cue-map > :is(img, picture, video) {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Anything else inside sits above the map. */
.cue-map > :not(.cue-map-canvas):not(img):not(picture):not(video) {
  position: relative;
  z-index: 2;
}

/* Attribution is a licence condition of most tile sources — keep it legible. */
.cue-map .leaflet-control-attribution {
  font-size: 11px;
  background: rgb(255 255 255 / 0.72);
}

/* Markup that only exists to declare pins shouldn't render. */
.cue-pin-source { display: none; }

/*
 * Pins. Leaflet positions `.cue-pin-icon` itself with a transform, so all our
 * animation happens on `.cue-pin` inside it — otherwise we'd be fighting
 * Leaflet for the same property and losing.
 */
.cue-pin-icon { background: none; border: 0; }

.cue-pin {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  /* Anchor the marker on its dot rather than its top-left corner. */
  translate: -7px -7px;
  white-space: nowrap;
  opacity: 0;
}

.cue-pin-dot {
  width: 14px;
  height: 14px;
  flex: none;
  border-radius: 50%;
  background: var(--cue-pin-color, #0b76ef);
  box-shadow: 0 0 0 3px rgb(255 255 255 / 0.9), 0 2px 6px rgb(0 0 0 / 0.4);
}

.cue-pin-label {
  font: 600 12px/1.2 ui-sans-serif, system-ui, sans-serif;
  color: var(--cue-pin-label-color, #10233b);
  background: var(--cue-pin-label-bg, rgb(255 255 255 / 0.92));
  padding: 0.25rem 0.45rem;
  border-radius: 5px;
  box-shadow: 0 2px 8px rgb(0 0 0 / 0.25);
}

/* ------------------------------------------------------------------ text */

/*
 * Split units must be inline-block, or a transform on them does nothing —
 * transforms don't apply to non-replaced inline boxes.
 */
.cue-split .cue-word,
.cue-split .cue-char {
  display: inline-block;
  will-change: transform, opacity;
}

/* Whitespace between words is a real text node, so wrapping still works. */
.cue-split .cue-word { white-space: pre-wrap; }

/*
 * The readable copy of split text. Off-screen but still in the accessibility
 * tree — `display: none` or `visibility: hidden` would remove it, which is
 * exactly what we're trying to avoid.
 */
.cue-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  white-space: nowrap;
  border: 0;
  clip-path: inset(50%);
}

/* ---------------------------------------------------------------- slides */

/* `:not()` as well as pin's doubled class — belt and braces, and it documents
   the interaction at the point where someone would otherwise add it back. */
.cue-slides {
  overflow: hidden;
}

.cue-slides:not(.cue-pin-target) {
  position: relative;
}

/*
 * Slides stack in the same box; only their transform separates them. They
 * start parked off to the right, so a JS failure leaves the first slide's
 * content readable rather than a pile of overlapping text.
 */
.cue-slide {
  position: absolute;
  inset: 0;
  will-change: transform;
}

.no-js .cue-slide,
.cue-init .cue-slide:not(:first-child) {
  position: relative;
}

/* ------------------------------------------------------------- crossfade */

.cue-crossfade {
  position: relative;
  overflow: hidden;
}

/* Children stack; only opacity separates them. */
.cue-crossfade > * {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* First child stays in flow so the container can size itself from content. */
.cue-crossfade > :first-child {
  position: relative;
}

/* ----------------------------------------------------------------- media */

/* Canvas fills the element; the fallback <img> underneath shows until the
   first real frame paints, then bows out. */
.cue-sequence {
  position: relative;
  overflow: hidden;
}

.cue-sequence-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.cue-sequence > img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.cue-sequence.is-painting > img {
  visibility: hidden;
}

/* Before/after: children stack; only the top one's clip-path moves. */
.cue-ba {
  position: relative;
  overflow: hidden;
}

.cue-ba > * {
  display: block;
  width: 100%;
}

.cue-ba > .cue-ba-top {
  position: absolute;
  inset: 0;
  height: 100%;
  object-fit: cover;
}

/* ------------------------------------------------------------------ form */

/*
 * The honeypot is moved out of sight rather than display:none — bots
 * increasingly skip fields that are display:none or type=hidden, and the
 * point is that they find this one.
 */
.cue-hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.cue-field-error {
  display: block;
  font-size: 0.85em;
}

.cue-form-status:empty { display: none; }

/* Post-send, the fields are done; the site can hide or fade them. */
.cue-form-status { margin: 0.75em 0 0; }

/* ---------------------------------------------------------------- search */

.cue-search { position: relative; }

.cue-search-results {
  list-style: none;
  margin: 0;
  padding: 0;
}

.cue-search-results:empty { display: none; }

.cue-search-results [role='option'] { cursor: pointer; }

.cue-search-title, .cue-search-excerpt { display: block; }

.cue-search-status:empty { display: none; }

/* ------------------------------------------------------------------ tilt */

/* Perspective lives on the parent, or rotateX/rotateY read as flat skew. */
.cue-tilt-scene { perspective: 800px; }

[data-cue~='tilt'] { will-change: transform; }

/* ------------------------------------------------------- reduced motion */

/*
 * Kill transitions rather than effects. The engine already settles
 * progress-driven effects to their end state; this stops the *transitions*
 * between states from animating.
 */
@media (prefers-reduced-motion: reduce) {
  .cue-header,
  .cue-submenu,
  .cue-caret {
    transition-duration: 0.01ms !important;
  }

  .cue-header.is-hidden,
  .cue-header.is-peek {
    transform: none;
    opacity: 1;
    pointer-events: auto;
  }

  .cue-header.is-peek::after { display: none; }
}
