/* ==========================================================================
   NC State Billboard News Slides
   Canvas: 1920 x 1080. All measurements below are in stage pixels; slide.js
   scales the whole stage to fit the player.

   Palette and type per brand.ncsu.edu:
     Wolfpack Red   #CC0000     Reynolds Red   #990000
     Wolfpack Black #000000     Grays          #333333 #4D4D4D #CCCCCC #F2F2F2
     Type: Roboto Condensed (headlines), Roboto (body), Roboto Slab (captions)
   ========================================================================== */

:root {
  --wolfpack-red: #CC0000;
  --reynolds-red: #990000;
  --black: #000000;
  --white: #FFFFFF;
  --gray-90: #333333;
  --gray-70: #4D4D4D;
  --gray-20: #CCCCCC;
  --gray-05: #F2F2F2;

  --media-width: 1000px;
  --rule-top: 14px;
  --footer-height: 86px;

  --bg: var(--white);
  --headline-color: var(--black);
  --body-color: var(--gray-90);
  --meta-color: var(--gray-70);
  --panel-bg: var(--white);
}

/*
 * --dept-accent is set per slide (not per stage) by slide.js /
 * events-cycle.js from config.php's per-department or per-event-type accent,
 * e.g. .slide { --dept-accent: #427E93 }. .eyebrow and .accent below read it
 * directly rather than through an intermediate variable: a custom property
 * inherits its *computed* value, so if .eyebrow's color instead read a
 * --eyebrow-color defined up at :root, the var(--dept-accent) inside it would
 * have already been resolved (to nothing, since :root has no --dept-accent)
 * before inheritance ever reached a .slide that sets one.
 */

/* Dark variant: ?theme=dark. Eyebrow stays white regardless of --dept-accent,
   because several palette colors do not clear WCAG AA on black at this size
   (see the contrast note in config.php). The accent bar still varies -- it is
   decorative, not text, so contrast against the black media panel does not
   apply the same way. */
.stage.theme-dark {
  --bg: var(--black);
  --panel-bg: var(--black);
  --headline-color: var(--white);
  --body-color: var(--gray-20);
  --meta-color: var(--gray-20);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: #000;
  overflow: hidden;
  cursor: none;
}

.stage {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 1920px;
  height: 1080px;
  transform-origin: 50% 50%;
  transform: translate(-50%, -50%);
  background: var(--bg);
  font-family: "Roboto", Arial, Helvetica, sans-serif;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

/* Start with red. */
.rule-top {
  position: absolute;
  inset: 0 0 auto 0;
  height: var(--rule-top);
  background: var(--wolfpack-red);
  z-index: 3;
}

.deck {
  position: absolute;
  top: var(--rule-top);
  left: 0;
  right: 0;
  bottom: var(--footer-height);
}

/* ------------------------------------------------------------------ slide */

.slide {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: var(--media-width) 1fr;
  /*
   * minmax(0, 1fr), not the implicit `auto`. An auto row is sized to its
   * content, so an overlong headline stretched the row past the bottom of
   * the frame and, worse, inflated the panel height that assets/fit.js
   * measures to decide whether that headline fits: the longer the text, the
   * more room it appeared to have. Pinning the row to the slide breaks that
   * loop and keeps content off the footer.
   */
  grid-template-rows: minmax(0, 1fr);
  opacity: 0;
  transition: opacity 700ms ease-in-out;
  will-change: opacity;
}

.slide.is-active { opacity: 1; }

/* -------------------------------------------------------------- the photo */

.media {
  position: relative;
  overflow: hidden;
  background: var(--black);
}

.media-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 40%; /* favors faces in portrait-style news photos */
  transform: scale(1.0);
}

/* Slow push, on by default, disable with ?kenburns=0 */
.slide.is-active .media-img.kenburns {
  animation: kenburns var(--dwell, 12s) linear forwards;
}

@keyframes kenburns {
  from { transform: scale(1.0); }
  to   { transform: scale(1.06); }
}

@media (prefers-reduced-motion: reduce) {
  .slide.is-active .media-img.kenburns { animation: none; }
  .slide { transition: none; }
}

/* A whisper of shadow along the photo's bottom edge gives every image a
   finished base, whatever its content. Decorative only, so it sits under
   no text and affects no contrast requirement. */
.media::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 180px;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.28), transparent);
  pointer-events: none;
}

/* Shown only when a post has no featured image. The two brand reds in a
   soft diagonal read as intentional art rather than a missing photo. */
.media-fallback {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, var(--wolfpack-red) 0%, var(--wolfpack-red) 55%, var(--reynolds-red) 100%);
}

.media-fallback span {
  font-family: "Roboto Condensed", Arial, sans-serif;
  font-weight: 700;
  font-size: 92px;
  color: var(--white);
  letter-spacing: 0.01em;
}

.slide.no-image .media-img { display: none; }
.slide.no-image .media-fallback { display: flex; }

/* --------------------------------------------------------------- the text */

.panel {
  position: relative;
  background: var(--panel-bg);
  display: flex;
  align-items: center;
  padding: 64px 80px;
  min-width: 0;
  min-height: 0;   /* grid items default to min-content; let it be constrained */
}

/* The two panels are the same opaque color, so a plain cross-dissolve would
   show both headlines stacked on top of each other for most of a second.
   Instead the outgoing text fades quickly, the photos dissolve, then the new
   text arrives line by line: accent bar first, then eyebrow, headline, date
   and abstract, each rising into place a beat after the one before. The
   stagger is what makes the change of story feel composed rather than
   swapped. */
.panel-inner {
  width: 100%;
  min-width: 0;
}

.panel-inner > * {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 260ms ease-in, transform 260ms ease-in;
}

.slide.is-active .panel-inner > * {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 420ms ease-out, transform 420ms cubic-bezier(0.22, 1, 0.36, 1);
}

.slide.is-active .panel-inner > :nth-child(1) { transition-delay: 380ms; }
.slide.is-active .panel-inner > :nth-child(2) { transition-delay: 460ms; }
.slide.is-active .panel-inner > :nth-child(3) { transition-delay: 540ms; }
.slide.is-active .panel-inner > :nth-child(4) { transition-delay: 620ms; }
.slide.is-active .panel-inner > :nth-child(5) { transition-delay: 700ms; }

@media (prefers-reduced-motion: reduce) {
  .panel-inner > *,
  .slide.is-active .panel-inner > * {
    transition: opacity 300ms ease;
    transform: none;
  }
}

/* The accent bar draws itself in left to right once its slide is up. */
.accent {
  display: block;
  width: 132px;
  height: 10px;
  background: var(--dept-accent, var(--wolfpack-red));
  margin-bottom: 26px;
  transform-origin: left center;
}

.slide.is-active .accent {
  animation: accent-draw 520ms cubic-bezier(0.22, 1, 0.36, 1) 380ms backwards;
}

@keyframes accent-draw {
  from { scale: 0 1; }
  to   { scale: 1 1; }
}

@media (prefers-reduced-motion: reduce) {
  .slide.is-active .accent { animation: none; }
}

/* Uppercase here is a label treatment, not body copy: ncsu.edu uses the same
   Roboto Condensed uppercase for its section labels. */
.eyebrow {
  margin: 0 0 20px;
  font-family: "Roboto Condensed", Arial, sans-serif;
  font-weight: 700;
  font-size: 28px;
  line-height: 1.2;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dept-accent, var(--wolfpack-red));
  /* "Civil, Construction and Environmental Engineering" needs two lines at
     this size. Balanced, it splits evenly instead of leaving one word
     stranded on the second line. */
  text-wrap: balance;
}

.stage.theme-dark .eyebrow { color: var(--white); }

.headline {
  margin: 0 0 24px;
  font-family: "Roboto Condensed", Arial, sans-serif;
  font-weight: 700;
  font-size: 78px;      /* slide.js shrinks this to fit */
  line-height: 1.06;
  letter-spacing: -0.005em;
  color: var(--headline-color);
  overflow-wrap: break-word;
  text-wrap: balance;
  /* No `overflow: hidden` here on purpose. assets/fit.js sizes the type to
     the space available, so there is nothing to hide, and hiding it would
     shave the descenders off the last line (the tail of a "y" or "g" sits a
     few pixels below its line box). The fitter sets overflow itself in the
     one case where it has to trim. */
}

/* Roboto Slab is the brand's secondary face for captions and standalone
   facts, which is exactly what a dateline is. */
.date {
  margin: 0 0 28px;
  font-family: "Roboto Slab", Georgia, serif;
  font-weight: 400;
  font-size: 28px;
  line-height: 1.3;
  color: var(--meta-color);
}

.abstract {
  margin: 0;
  font-family: "Roboto", Arial, sans-serif;
  font-weight: 400;
  font-size: 33px;
  line-height: 1.45;
  color: var(--body-color);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 5;
  line-clamp: 5;
  overflow: hidden;
}

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

/* End with red. */
.footer {
  position: absolute;
  inset: auto 0 0 0;
  height: var(--footer-height);
  background: var(--wolfpack-red);
  z-index: 3;
}

.progress {
  position: absolute;
  top: 0;
  left: 0;
  height: 7px;
  width: 0;
  background: var(--white);
}

.progress.run { animation: fill var(--dwell, 12s) linear forwards; }

@keyframes fill { from { width: 0; } to { width: 100%; } }

.footer-inner {
  height: 100%;
  padding: 0 80px 0 80px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.footer-source {
  font-family: "Roboto Condensed", Arial, sans-serif;
  font-weight: 700;
  font-size: 32px;
  color: var(--white);
  letter-spacing: 0.01em;
}

.dots {
  display: flex;
  gap: 16px;
  align-items: center;
}

.dot {
  width: 16px;
  height: 16px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.42);
  transition: width 360ms cubic-bezier(0.22, 1, 0.36, 1), background 360ms ease;
}

/* The active dot stretches into a short bar: easier to spot from across a
   lobby than a brightness change alone. */
.dot.is-active {
  width: 44px;
  background: var(--white);
}

@media (prefers-reduced-motion: reduce) {
  .dot { transition: none; }
}

/* ---------------------------------------------------------- status screen */

.status {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  padding: 0 140px;
  background: var(--wolfpack-red);
  color: var(--white);
  z-index: 4;
}

.status h2 {
  margin: 0 0 20px;
  font-family: "Roboto Condensed", Arial, sans-serif;
  font-weight: 700;
  font-size: 88px;
  line-height: 1.05;
}

.status p {
  margin: 0;
  font-family: "Roboto", Arial, sans-serif;
  font-size: 34px;
  line-height: 1.4;
  max-width: 1200px;
}

.status[hidden] { display: none; }
