/* ===========================================================================
   DotCity Hub Banner + Command Layer
   Candidate stylesheet. Proposed final path: assets/css/hub-banner.css
   Prefix: .dch-  (DotCity Hub) - deliberately collision-free against
   .masthead*, .mast-*, .nb5-*, .hub-*, .ev-*, .area-hub-* already in the theme.

   Written to win on its own specificity except for the identity text, which
   must overrule legacy hub colour declarations loaded after this file.
   Nothing in this file may ever be patched at runtime.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   1. Tokens
   Every hub identity is expressed ONLY through these five custom properties
   plus its media and copy. No hub gets its own selector.
   --------------------------------------------------------------------------- */
.dch {
  --dch-accent: #E8201A;      /* identity colour: eyebrow dot, focus, add btn */
  --dch-accent-ink: #ffffff;  /* text drawn on --dch-accent (must pass 4.5:1) */
  --dch-base: #0b0b0e;        /* solid backstop behind the media             */
  --dch-veil: 6, 6, 9;        /* scrim colour as an r,g,b triplet            */
  --dch-focus: 50% 50%;       /* object-position for the media               */

  --dch-bleed: 0px;           /* set by the page = the CENTRE's inline padding */
  --dch-pad-i: clamp(16px, 4.4vw, 56px);
  --dch-pad-b: clamp(18px, 3.2vw, 34px);
  --dch-min: 300px;
  --dch-radius: 0px;
  --dch-tap: 44px;            /* minimum interactive target                  */
  --dch-feather: 104px;       /* soft top edge on the guaranteed text scrim  */
}

/* ---------------------------------------------------------------------------
   2. Shell
   Full available width after the navigation. The page hands the component its
   own container padding as --dch-bleed and the banner cancels it. No
   per-hub negative-margin rule, no calc(100% + 104px) magic numbers.
   --------------------------------------------------------------------------- */
.dch {
  container-type: inline-size;
  container-name: dch;
  position: relative;
  z-index: 0;
  isolation: isolate;
  display: grid;
  /* minmax(0,1fr), not auto: an auto track is max-content sized, and the
     horizontal quick-action rail would then stretch the track past the banner
     and be clipped instead of scrolled. */
  grid-template-columns: minmax(0, 1fr);
  align-content: end;
  box-sizing: border-box;
  width: calc(100% + (var(--dch-bleed) * 2));
  margin-inline: calc(var(--dch-bleed) * -1);
  min-height: var(--dch-min);
  overflow: hidden;
  border: 0;
  border-radius: var(--dch-radius);

  /* Unconditional. Not inside a media query, so a missing, failed, slow or
     wrongly-proportioned media can never expose the page background. */
  background-color: var(--dch-base);
  color: #ffffff;
}

/* Migrated static hubs own the first content slot. Cancel the legacy page
   frame so the banner is flush with the content rail and starts at its top. */
main.CENTRE.static-prototype-page > .dch {
  width: calc(100% + 56px) !important;
  margin-inline: -28px !important;
  margin-top: -28px !important;
  border-radius: 0;
}

@media (max-width: 900px) {
  main.CENTRE.static-prototype-page > .dch {
    width: calc(100% + 20px) !important;
    margin-inline: -10px !important;
    margin-top: -52px !important;
  }
}

/* ---------------------------------------------------------------------------
   3. Media
   The media box is absolutely positioned with a definite height, so height:100%
   resolves against it. `object-fit:cover` then fills without stretching.
   `height:auto` on a replaced element is what produced the blank bands and is
   forbidden here.
   --------------------------------------------------------------------------- */
.dch-media {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  background-color: var(--dch-base);
}

.dch-media > img,
.dch-media > video {
  display: block;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  object-fit: cover;
  object-position: var(--dch-focus);
  border: 0;
}

/* A <video> with no playable source paints its poster; with no poster either it
   paints nothing and --dch-base shows through. Both are covered states. */

/* ---------------------------------------------------------------------------
   4. Scrim - deterministic contrast
   Two layers:
     a) a flat wash over the whole banner, which tames bright highlights;
     b) a block behind the content box only, extended upward and feathered.
   Because (b) covers 100% of the text box at a known alpha, white text has a
   guaranteed contrast ratio against ANY media, including pure white.
       flat wash            0.24
       content block        0.90
       combined over white  1 - (0.10 x 0.76) = 0.924
       resulting luminance  0.076  ->  white contrast 8.3:1
   --------------------------------------------------------------------------- */
.dch-scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background-color: rgba(var(--dch-veil), 0.24);
}

/* Food is motion media and remains legible with a lighter treatment. */
.dch[data-hub="food"] .dch-scrim {
  background-color: rgba(var(--dch-veil), 0.12);
}

.dch-inner {
  position: relative;
  z-index: 2;
  box-sizing: border-box;
  width: 100%;
  min-width: 0;
  max-width: 1180px;
  margin-inline: auto;
  padding: 0 var(--dch-pad-i) var(--dch-pad-b);
  padding-block-start: 8px;
}

/* Bled to the full banner width with 100cqw, not just the 1180px content
   column, so a wide viewport never shows a lit strip beside the text. */
.dch-inner::before {
  content: "";
  position: absolute;
  top: calc(var(--dch-feather) * -1);
  bottom: 0;
  left: 50%;
  width: 100cqw;
  transform: translateX(-50%);
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(
    0deg,
    rgba(var(--dch-veil), 0.90) 0,
    rgba(var(--dch-veil), 0.90) calc(100% - var(--dch-feather)),
    rgba(var(--dch-veil), 0.00) 100%
  );
}

.dch[data-hub="food"] .dch-inner::before {
  background: linear-gradient(
    0deg,
    rgba(var(--dch-veil), 0.72) 0,
    rgba(var(--dch-veil), 0.72) calc(100% - var(--dch-feather)),
    rgba(var(--dch-veil), 0.00) 100%
  );
}

/* ---------------------------------------------------------------------------
   5. Identity block
   --------------------------------------------------------------------------- */
.dch .dch-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin: 0 0 9px;
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #ffffff !important;
}

.dch-eyebrow i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--dch-accent);
  box-shadow: 0 0 0 3px rgba(var(--dch-veil), 0.45);
  animation: dch-pulse 2s ease-in-out infinite;
}

@keyframes dch-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }

.dch .dch-title {
  margin: 0 0 8px;
  font-weight: 900;
  font-size: clamp(1.85rem, 6.4cqw, 3rem);
  line-height: 1.02;
  letter-spacing: -0.03em;
  color: #ffffff !important;
  text-wrap: balance;
}

.dch .dch-dek {
  margin: 0;
  max-width: 54ch;
  font-size: clamp(0.86rem, 1.6cqw, 0.96rem);
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.88) !important;
}

/* Freshness / provenance. Rendered only when the server has a real timestamp
   and a real source name. Never a default string. */
.dch-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  margin: 12px 0 0;
  padding: 0;
  list-style: none;
  font-size: 0.68rem;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.82);
}

.dch-meta li { display: inline-flex; align-items: center; gap: 6px; }

.dch-meta li::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.5);
}

/* Commercial disclosure. Required wherever placement was paid for. */
.dch-flag--paid {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px;
  border: 1px solid rgba(255, 255, 255, 0.42);
  border-radius: 999px;
  font-size: 0.6rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #ffffff;
}

/* ---------------------------------------------------------------------------
   6. Command layer
   Search and Add always share one line. The grid track for the add control is
   fixed at the tap size, so it can never be pushed onto a row of its own.
   --------------------------------------------------------------------------- */
.dch-command {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--dch-tap);
  /* stretch, not center: the search pill carries a 1px border and the Add
     control does not, so centring leaves them a subpixel out of line. */
  align-items: stretch;
  gap: 8px;
  width: 100%;
  max-width: 560px;
  margin-top: 16px;
}

/* Real form. Submits to a real results route with a real query parameter. */
.dch-search {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--dch-tap);
  align-items: stretch;
  min-height: var(--dch-tap);
  padding: 0 0 0 16px;
  border: 1px solid rgba(255, 255, 255, 0.5);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.97);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.24);
}

/* align-self:stretch, so the whole 44px row focuses the field rather than only
   the 20px text box. */
.dch-search input {
  align-self: stretch;
  min-width: 0;
  padding: 0 4px 0 0;
  border: 0;
  outline: 0;
  background: transparent;
  font: inherit;
  font-size: 0.86rem;
  color: #111114;
}

.dch-search input::placeholder { color: #5f6672; }

/* A full-height end cap: the submit target is the same 44px as every other
   primary control, not a 34px dot inside it. */
.dch-search button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--dch-tap);
  min-height: var(--dch-tap);
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: #111114;
  color: #ffffff;
  cursor: pointer;
}

.dch-search button svg { width: 15px; height: 15px; }

/* Compact add / submit action. Circle on small containers, labelled pill on
   large ones. The accessible name is on the control at every size. */
.dch-add {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  width: var(--dch-tap);
  min-height: var(--dch-tap);
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: var(--dch-accent);
  color: var(--dch-accent-ink);
  font-size: 0.8rem;
  font-weight: 800;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

.dch-add svg { width: 17px; height: 17px; flex: 0 0 auto; }
.dch-add span { display: none; }

/* Time switch. Real links to filtered routes, never a client-only toggle. */
.dch-when {
  display: inline-flex;
  margin-top: 14px;
  padding: 3px;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 999px;
  background: rgba(var(--dch-veil), 0.55);
}

.dch-when a {
  display: inline-flex;
  align-items: center;
  min-height: var(--dch-tap);
  padding: 0 16px;
  border-radius: 999px;
  font-size: 0.76rem;
  font-weight: 700;
  color: rgba(255, 255, 255, 0.86);
  text-decoration: none;
}

.dch-when a[aria-current="page"] {
  background: #ffffff;
  color: #111114;
}

/* Quick actions. A full-bleed scroll rail: it never wraps, so it never leaves
   a ragged second row or a dead gap under the banner. */
.dch-quick {
  display: flex;
  min-width: 0;
  gap: 8px;
  margin: 16px calc(var(--dch-pad-i) * -1) 0;
  padding: 2px var(--dch-pad-i);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
}

.dch-quick::-webkit-scrollbar { display: none; }

.dch-quick a,
.dch-quick button {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 7px;
  min-height: var(--dch-tap);
  padding: 0 15px;
  border: 1px solid rgba(255, 255, 255, 0.34);
  border-radius: 999px;
  background: rgba(var(--dch-veil), 0.5);
  color: #ffffff;
  font-size: 0.78rem;
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
  scroll-snap-align: start;
  cursor: pointer;
}

.dch-quick svg { width: 15px; height: 15px; flex: 0 0 auto; }

.dch-quick [aria-pressed="true"] {
  border-color: var(--dch-accent);
  background: var(--dch-accent);
  color: var(--dch-accent-ink);
}

/* An empty module disappears. Belt-and-braces: the server should not emit it. */
.dch-meta:empty,
.dch-when:empty,
.dch-quick:empty,
.dch-command:empty { display: none; }

/* Sparse areas: the same component, fewer modules and an honest line. No
   invented counts, no placeholder cards, no "0 events" chips. */
.dch[data-density="sparse"] .dch-note {
  display: block;
  margin-top: 14px;
  font-size: 0.76rem;
  color: rgba(255, 255, 255, 0.82);
}

.dch-note { display: none; }

/* ---------------------------------------------------------------------------
   7. Container-driven layout
   Container queries, not viewport queries, so the component is correct inside
   a 216px-rail hub page, a 240px-rail area page and a standalone preview.
   --------------------------------------------------------------------------- */
/* Height is content-driven above a fixed floor. A container query cannot style
   its own container, so the banner never sets its height from a @container
   block - it grows from the inner padding, which a container query CAN set.
   This also removes any chance of a fixed height fighting the content. */
@container dch (min-width: 700px) {
  .dch-inner {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
    column-gap: clamp(24px, 4cqw, 64px);
    padding-block-start: 96px;
  }

  .dch-identity { grid-column: 1; }

  .dch-command {
    grid-column: 2;
    grid-row: 1;
    align-self: end;
    width: min(430px, 40cqw);
    grid-template-columns: minmax(0, 1fr) auto;
    margin-top: 0;
  }

  .dch-add { width: auto; padding: 0 18px; }
  .dch-add span { display: inline; }

  .dch-when,
  .dch-quick { grid-column: 1 / -1; }

  /* A grid item stretches by default. The time switch is a pill, not a bar,
     so it must be pinned to the start of its track. */
  .dch-when { justify-self: start; }
}

@container dch (min-width: 1100px) {
  .dch-inner { --dch-feather: 132px; padding-block-start: 132px; }
}

/* ---------------------------------------------------------------------------
   8. Focus and keyboard
   --------------------------------------------------------------------------- */
.dch a:focus-visible,
.dch button:focus-visible,
.dch input:focus-visible {
  outline: 3px solid #ffffff;
  outline-offset: 2px;
  border-radius: 999px;
}

.dch-search:focus-within {
  border-color: #111114;
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.9), 0 6px 20px rgba(0, 0, 0, 0.24);
}

.dch-skip {
  position: absolute;
  left: var(--dch-pad-i);
  top: 8px;
  z-index: 5;
  padding: 10px 16px;
  border-radius: 999px;
  background: #ffffff;
  color: #111114;
  font-size: 0.78rem;
  font-weight: 800;
  text-decoration: none;
  transform: translateY(-200%);
}

.dch-skip:focus-visible { transform: none; }

/* ---------------------------------------------------------------------------
   9. Reduced motion
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .dch *,
  .dch *::before,
  .dch *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }

  .dch-eyebrow i { opacity: 1; }
  .dch-quick { scroll-behavior: auto; scroll-snap-type: none; }
}

/* Reduced-motion timing overrides below are deliberately important. */

/* --------------------------------------------------------------------------
   10. Forced colours
   -------------------------------------------------------------------------- */
@media (forced-colors: active) {
  .dch-scrim, .dch-inner::before { display: none; }
  .dch { background: Canvas; color: CanvasText; }
  .dch-title, .dch-dek, .dch-eyebrow, .dch-meta { color: CanvasText; }
  .dch-quick a, .dch-quick button, .dch-when a, .dch-add { border: 1px solid ButtonBorder; }
}
