/* ============================================================================
   Verity Gulf - container, grid, section rhythm, header and footer skeleton.
   Structure only. What things look like lives in components.css.
   ========================================================================= */

/* ---------------------------------------------------------------- container */

.container {
  inline-size: min(100% - (var(--gutter) * 2), var(--container));
  margin-inline: auto;
}

.container--wide    { --container: var(--container-wide); }
.container--narrow  { --container: var(--container-narrow); }
.container--reading { --container: var(--container-reading); }

/* ---------------------------------------------------------------- sections */

.section { padding-block: var(--sec-y); }
.section--tight { padding-block: var(--s-9); }
.section--flush { padding-block: 0; }
.section--alt { background: var(--bg-canvas-2); }
.section--surface { background: var(--bg-surface); }

/*
 * Was content-visibility: auto, to skip layout for off-screen sections.
 * Removed: it also stops an IntersectionObserver inside the section from ever
 * firing, which left every scroll-revealed card at opacity 0. A rendering
 * saving is not worth a section that can silently go blank, so the class is
 * kept as a marker and does nothing.
 */
.section--deferred { /* intentionally empty - see the note above */ }

/* ---------------------------------------------------------------- grid */

.grid {
  display: grid;
  gap: var(--grid-gap);
}

.grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

/* The portal's named layouts, each collapsing on its own schedule in
   responsive.css rather than through a pile of utility classes.

   Each declares display:grid itself. They read as modifiers of .grid, but the
   templates use them on their own, and a layout class that silently does
   nothing without a second class is a trap. */
.grid--hero,
.grid--lead,
.grid--listing,
.grid--article,
.grid--triple,
.grid--feature {
  display: grid;
  gap: var(--grid-gap);
}

.grid--hero    { grid-template-columns: 300px minmax(0, 1fr) 340px; }
.grid--lead    { grid-template-columns: 5fr 4fr 3fr; }
.grid--listing { grid-template-columns: minmax(0, 8fr) minmax(0, 4fr); gap: var(--s-8); align-items: start; }
/* The reading column is a fixed measure, not a fraction: with 1fr the cell
   grew to whatever the container left over and the text, capped at the
   measure, sat against the left of it - so the page read as though it had
   slipped off centre. Sizing both tracks and centring the pair puts the
   leftover space on both sides instead of all of it in the middle. */
.grid--article {
  /* How far the lead media is allowed to widen past the reading column. The
     gap has to clear it on the side facing the sidebar and still leave the
     two apart, or a bleeding photograph slides under the trending rail. */
  --article-bleed: 60px;

  grid-template-columns: minmax(0, var(--container-reading)) 340px;
  gap: calc(var(--article-bleed) + var(--s-5));
  align-items: start;
  justify-content: center;
}
.grid--triple  { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid--feature { grid-template-columns: 58fr 42fr; gap: var(--s-6); }

/* A rail that becomes a horizontal scroller on small screens. The class is
   applied at every width; the scrolling behaviour is switched on in
   responsive.css so the markup never changes. */
.rail {
  display: grid;
  gap: var(--grid-gap);
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
}

/* ---------------------------------------------------------------- page shell */

.site-main { min-block-size: 60vh; }

/* Pages that do not open with a full-bleed hero start below the header. */
.site-main--offset { padding-block-start: var(--s-6); }

/* ---------------------------------------------------------------- header */

.site-header {
  position: relative;
  z-index: var(--z-header);
  background: var(--bg-surface);
}

/* Strip 1 - the dark utility bar. Full bleed. */
.utility-bar {
  background: var(--c-brand-900);
  color: rgba(255, 255, 255, .82);
  font-size: var(--fs-xs);
  font-weight: 500;
}

.utility-bar__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  min-block-size: 40px;
}

.utility-bar__group {
  display: flex;
  align-items: center;
  gap: var(--s-3);
}

/* Strip 2 - the masthead. */
.masthead {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--s-4);
  min-block-size: 72px;
}

.masthead__start { justify-self: start; }
.masthead__brand { justify-self: center; }

.masthead__end {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s-2);
}

/* Strip 3 - the section nav. This is the strip that sticks. */
.section-nav {
  background: var(--bg-surface);
  border-block-end: var(--hairline) solid var(--line);
}

.section-nav__inner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-7);
  min-block-size: 56px;
}

/*
 * Sticky state. An IntersectionObserver sentinel above the header toggles
 * .is-stuck on <body>, rather than the nav watching scroll position itself -
 * scroll maths in JS costs a layout read on every frame.
 */
.section-nav.is-stuck {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: var(--z-header);
  min-block-size: var(--header-h);
  box-shadow: var(--sh-sm);
}

.section-nav.is-stuck .section-nav__inner {
  justify-content: space-between;
  min-block-size: var(--header-h);
}

/* The compact wordmark and actions only exist once the nav is stuck. */
.section-nav__compact { display: none; }
.section-nav.is-stuck .section-nav__compact { display: flex; align-items: center; gap: var(--s-2); }

/* The sentinel the observer watches. Zero height, no paint. */
.header-sentinel {
  block-size: 1px;
  margin-block-end: -1px;
  pointer-events: none;
}

/* Strip 5 - trending topics. */
.topics {
  background: var(--bg-canvas);
}

.topics__inner {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  min-block-size: 48px;
}

/* ---------------------------------------------------------------- footer */

/*
 * The gap above the footer is a margin, not padding: it has to stay the page
 * colour rather than the footer's, so the dark band starts where the footer
 * does. The footer used to butt straight against whatever band ended the page,
 * which on a story left the comment form touching it.
 */
.site-footer {
  margin-block-start: 50px;
  background: var(--c-brand-900);
  color: rgba(255, 255, 255, .78);
  padding-block-start: var(--s-12);
}

.site-footer__grid {
  display: grid;
  grid-template-columns: 1.6fr 1fr 1fr 1fr 1.4fr;
  gap: var(--s-9);
  padding-block-end: var(--s-12);
}

.site-footer__bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--s-4);
  min-block-size: 68px;
  border-block-start: 1px solid rgba(255, 255, 255, .12);
  font-size: var(--fs-xs);
  font-weight: 500;
}

/* ---------------------------------------------------------------- sidebar */

.sidebar {
  display: flex;
  flex-direction: column;
  gap: var(--s-6);
}

/* The sidebar tracks the article until its own end, then stops. */
.sidebar--sticky {
  position: sticky;
  inset-block-start: calc(var(--header-h) + var(--s-7));
  align-self: start;
}

/* ---------------------------------------------------------------- fixed bars */

/* Anything pinned to an edge honours the phone's safe area, or it sits under
   the home indicator on iOS. */
/*
 * --cookie-bar-h is the consent notice's measured height, published by
 * cookie.js while the notice is up and zeroed when it goes. The notice covers
 * the whole bottom edge, so without this the button sits underneath it - and
 * at some widths the notice's Decline button lands exactly where this one is,
 * which turns "back to top" into "decline cookies".
 */
.back-to-top {
  position: fixed;
  inset-block-end: calc(var(--s-6) + var(--cookie-bar-h, 0px) + env(safe-area-inset-bottom, 0px));
  inset-inline-end: calc(var(--s-6) + env(safe-area-inset-right, 0px));
  z-index: var(--z-sticky);
  transition: inset-block-end var(--t-base) var(--ease);

  display: grid;
  place-items: center;
  inline-size: 46px;
  block-size: 46px;
  background: var(--brand);
  color: var(--on-brand);
  border: 0;
  border-radius: 50%;
  box-shadow: var(--sh-md);
  cursor: pointer;
  transition: background var(--t-base) var(--ease),
              transform var(--t-base) var(--ease);
}

/*
 * The button is shown and hidden through the hidden attribute, and the rule
 * above sets display - which beats the browser's own [hidden] { display: none }
 * on specificity. Without this line the button would sit there permanently,
 * including at the top of the page where it points nowhere.
 */
.back-to-top[hidden] { display: none; }

@media (hover: hover) {
  .back-to-top:hover { background: var(--brand-hover); transform: translateY(-2px); }
}

.back-to-top:active { transform: translateY(0) scale(.94); }

@media (prefers-reduced-motion: reduce) {
  .back-to-top:hover,
  .back-to-top:active { transform: none; }
}

/* A phone's thumb reach is worth more than the last few pixels of margin. */
@media (max-width: 575.98px) {
  .back-to-top {
    inset-block-end: calc(var(--s-4) + var(--cookie-bar-h, 0px) + env(safe-area-inset-bottom, 0px));
    inset-inline-end: calc(var(--s-4) + env(safe-area-inset-right, 0px));
  }
}

.cookie-bar {
  position: fixed;
  inset-block-end: 0;
  inset-inline: 0;
  z-index: var(--z-toast);
  padding-block-end: calc(var(--s-4) + env(safe-area-inset-bottom, 0px));
}
