/* PerfectCMS additions for the eSign design. esign.css stays untouched. */

body { margin: 0; }

/* Uploaded media fills its frame. While a slot is empty, the CMS removes the
   <img>/<video> and the designer's caption shows instead. */
.media > img, .media > video, .hero-media > video {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
}
.media > img ~ .cap, .media > video ~ .cap, .hero-media > video ~ .cap { display: none; }
.hero-media > video { opacity: .55; }

/* Projects: the design gives tiles 1 and 2 a larger size. With a repeat the
   CMS outputs the same markup for every tile, so size by position instead. */
.pgrid > .tile { grid-column: span 4; }
.pgrid > .tile:nth-child(1) { grid-column: span 7; }
.pgrid > .tile:nth-child(2) { grid-column: span 5; }
.pgrid > .tile:nth-child(1) .media { aspect-ratio: 4 / 3; }
.pgrid > .tile:nth-child(2) .media { aspect-ratio: 4 / 5; }
.pgrid > .tile:nth-child(n+3) .media { aspect-ratio: 1 / 1; }
@media (max-width: 960px) { .pgrid > .tile:nth-child(n) { grid-column: span 6; } }
@media (max-width: 640px) { .pgrid > .tile:nth-child(n) { grid-column: span 12; } }

/* The hidden attribute must win over layout classes such as .stack (display: flex),
   so the mobile menu and the form's success message stay hidden until needed. */
[hidden] { display: none !important; }

/* Form feedback. PerfectCMS adds .pcms-form-error under a field and
   .pcms-form-alert above the form. */
[data-form-error], .pcms-form-error, .pcms-form-alert { color: #FFD7DA; font-size: 14px; margin: 4px 0 0; }
.sec:not(.inkbg):not(.dark) :is([data-form-error], .pcms-form-error, .pcms-form-alert) { color: var(--red-dk); }

/* Choice chips in forms are real radio buttons styled as the design's chips. */
label.chip { display: inline-flex; align-items: center; position: relative; }
label.chip:has(input:checked) { background: var(--ink); border-color: var(--ink); color: #fff; }
label.chip:focus-within { outline: 2px solid var(--red); outline-offset: 2px; }
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* Signs overview: the CMS repeats one item; alternate the photo side like the design. */
[data-alternate] > article:nth-child(even) > .media { order: 2; }

/* A guide or city without a page yet is shown as plain text. */
span.tile { cursor: default; }

/* Menus: the CMS repeats one <li> per item. In the header the list and its
   items take no box, so the links stay direct flex items of the nav. */
.menu { display: contents; list-style: none; margin: 0; padding: 0; }
.menu > li { display: contents; }
.nav a[aria-current="page"] { background: rgba(23,25,28,.06); }
.ftr-bot ul { list-style: none; margin: 0; padding: 0; }
/* Legal links sit side by side (the footer's column rule would stack them). */
.ftr .ftr-bot ul.row { flex-direction: row; }
/* The CMS marks the current page: aria-current on the link, .is-current on its <li>. */
.mpanel .is-current > a, .mpanel a[aria-current="page"] { text-decoration: underline; text-underline-offset: 4px; }
.ftr .is-current > a, .ftr a[aria-current="page"] { color: #fff; }

/* Rich text slots render inside <div class="rt"> (never <p>), so editors can
   add paragraphs, lists and headings without breaking the markup. */
.rt > :is(p, ul, ol, h2, h3, h4, blockquote) + :is(p, ul, ol, h2, h3, h4, blockquote) { margin-top: .75em; }
.rt :is(ul, ol) { padding-left: 1.25em; margin: 0; }
.faq details > div { padding: 0 0 24px; max-width: 68ch; color: var(--steel); }
/* Timeline step text is a block now; keep the line spacing the design had as inline text. */
.tl li > .rt { line-height: calc(17px * 1.55); }
/* Wrapper for a repeat of one entry that must not add a box. */
.contents { display: contents; }

/* ------------------------------------------------------------------ Phones and tablets */

/* A photo frame's aspect ratio must never make it wider than its column
   (the 21:9 frames with a 180px minimum height were 420px wide on a 360px phone). */
.media { min-width: 0; max-width: 100%; }

/* Match my quote: three steps in a row on wide screens, then the design's .tl breakpoints. */
.tl.tl-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

/* Areas we deliver to: city links. */
.linklist { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 8px; }

@media (max-width: 960px) {
  .tl.tl-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* Touch targets of at least 44px for links outside running text. */
  .logo, .crumbs a, .textlink, .ftr a, .rl a, a.d2, .linklist a, .spec th a, .stack > a:not(.btn):not(.tile) {
    display: inline-flex; align-items: center; min-height: 44px;
  }
  .crumbs { align-items: center; }
  .ftr ul, .linklist { gap: 0; }
  .ftr .row { column-gap: 18px; row-gap: 0; flex-wrap: wrap; }
  .drop input[type="file"] { min-height: 44px; }
}

@media (max-width: 640px) {
  .r21 { aspect-ratio: 16 / 9; }
  .tl.tl-3 { grid-template-columns: minmax(0, 1fr); }

  /* Long button labels wrap instead of pushing past the screen edge. */
  .btn { white-space: normal; text-align: center; }
  .row > .btn { flex: 1 1 auto; }
}

/* ------------------------------------------------------------------ Logo
   Header and footer show the Logo from Website controls › Branding through their
   "logo" slot (an editor can override it per partial under Pages › Site-wide slots).
   If the CMS leaves the <img> without a src, site.js fills it from the page's
   Organization logo. An <img> with no src is hidden, so the red mark and the site
   name show instead; once a logo shows, the name stays for screen readers only. */
.logo-img { display: block; height: 48px; width: auto; max-width: 240px; object-fit: contain; }
.logo-img:not([src]), .logo-img[src=""] { display: none; }
.logo:has(.logo-img[src]:not([src=""])) .logo-mark { display: none; }
.logo:has(.logo-img[src]:not([src=""])) .logo-name {
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}
/* The footer is near-black: show the logo in white. */
.ftr .logo-img { height: 56px; max-width: 260px; filter: brightness(0) invert(1); }
@media (max-width: 640px) {
  .hdr .logo-img { height: 40px; max-width: 180px; }
}

/* Breadcrumbs on dark sections keep the section's colour (the Home link is a shared partial). */
.crumbs.on-dark a { color: inherit; }

/* Scroll motion (7.4.0). site.js marks blocks with data-reveal and adds
   .reveal-on to <html> only when the motion can run, so without JavaScript,
   in the CMS editor and for "reduce motion" users everything shows as normal. */
.reveal-on [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 36px, 0);
}
.reveal-on [data-reveal="left"]  { transform: translate3d(-56px, 0, 0); }
.reveal-on [data-reveal="right"] { transform: translate3d(56px, 0, 0); }
.reveal-on [data-reveal="zoom"]  { transform: scale(.94); }
/* The transition sits on the revealed state only, so blocks hide instantly on
   load and animate only on the way in. */
.reveal-on [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
  transition: opacity .8s cubic-bezier(.16, 1, .3, 1), transform .9s cubic-bezier(.16, 1, .3, 1);
  transition-delay: var(--reveal-delay, 0ms);
}

/* Sideways slides must not widen the page. */
.reveal-on main, .reveal-on footer { overflow-x: clip; }

/* Photos drift with the scroll; slightly enlarged so their edges never show. */
[data-parallax] {
  transform: translate3d(0, var(--parallax, 0px), 0) scale(1.08);
  will-change: transform;
}

/* Phones: shorter, vertical-only movement. */
@media (max-width: 640px) {
  .reveal-on [data-reveal],
  .reveal-on [data-reveal="left"],
  .reveal-on [data-reveal="right"] { transform: translate3d(0, 24px, 0); }
  .reveal-on [data-reveal].is-revealed { transform: none; }
}

@media print {
  .reveal-on [data-reveal] { opacity: 1 !important; transform: none !important; }
  [data-parallax] { transform: none !important; }
}
@media (prefers-reduced-motion: reduce) {
  .reveal-on [data-reveal] { opacity: 1 !important; transform: none !important; }
  [data-parallax] { transform: none !important; }
}

/* ------------------------------------------------------------------ Sign cards: image + video (7.6.0)
   Home › What we make, the Signs page and the sign's own page (7.8.0). Each card holds the product image and,
   when the product has one, its video as a second slide. Visitors swipe (touch),
   drag (mouse), tap the dots or tap "Swipe for video". site.js builds the dots,
   shows the label, plays the video on a loop while the card is on screen and
   nudges the card open once so the video is noticed. One slide: no swiping. */
.swipe {
  position: absolute; inset: 0; display: flex;
  overflow-x: auto; overflow-y: hidden; scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain; scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.swipe::-webkit-scrollbar { display: none; }
.swipe > .swipe-slide {
  flex: 0 0 100%; width: 100%; height: 100%; object-fit: cover; display: block;
  scroll-snap-align: start; scroll-snap-stop: always; user-select: none; -webkit-user-drag: none;
}
.swipe.is-dragging, .swipe.is-peeking { scroll-snap-type: none; }
.swipe.is-dragging { cursor: grabbing; }
[data-swipe].has-video .swipe { cursor: grab; }
.media:has(.swipe-slide) > .cap { display: none; }

/* "Swipe for video" label: a red pill on the image slide; hidden once the video shows. */
.swipe-hint {
  position: absolute; top: 12px; left: 12px; z-index: 2;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px 7px 10px; border-radius: 999px;
  background: var(--red); color: #fff;
  font-size: 13px; font-weight: 600; line-height: 1; letter-spacing: .01em;
  box-shadow: 0 4px 14px rgba(0,0,0,.28); cursor: pointer;
  transition: opacity .3s ease, transform .3s ease;
}
.swipe-hint::after { content: "\2192"; font-weight: 700; animation: swipe-arrow 1.4s ease-in-out infinite; }
.swipe-hint.is-gone { opacity: 0; transform: translateY(-6px); pointer-events: none; }
@keyframes swipe-arrow { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(4px); } }

/* Dots: one per slide; the video's dot is a small play mark. */
.swipe-dots {
  position: absolute; left: 0; right: 0; bottom: 12px; z-index: 2;
  display: flex; justify-content: center; align-items: center; gap: 8px; pointer-events: none;
}
.swipe-dots > span {
  pointer-events: auto; cursor: pointer; width: 8px; height: 8px; border-radius: 50%;
  background: rgba(255,255,255,.55); box-shadow: 0 0 0 1px rgba(0,0,0,.2); transition: background .2s, transform .2s;
}
.swipe-dots > span.is-video {
  width: 0; height: 0; border-radius: 0; background: none; box-shadow: none;
  border-style: solid; border-width: 5px 0 5px 9px; border-color: transparent transparent transparent rgba(255,255,255,.6);
  filter: drop-shadow(0 0 1px rgba(0,0,0,.5));
}
.swipe-dots > span.on { background: #fff; transform: scale(1.25); }
.swipe-dots > span.is-video.on { background: none; border-left-color: #fff; }
/* Bigger tap area for the dots on touch screens. */
@media (pointer: coarse) { .swipe-dots { gap: 14px; } .swipe-dots > span { outline: 10px solid transparent; } }

@media (prefers-reduced-motion: reduce) { .swipe-hint::after { animation: none; } }

/* City pages (7.7.1): no photo placeholders. Without an intro photo the intro
   text takes the full width; project tiles without a photo show text only. */
.city-head:not(:has(> .media)) { grid-template-columns: minmax(0, 1fr); }
.city-head:not(:has(> .media)) > .stack-l { max-width: 920px; }
