/* =====================================================================
   CS Masterclass — design system
   Loaded by every prototype page. Phase 3 will port these to Tailwind v4
   tokens + a few raw CSS layers in the Next.js + Payload build.
   ===================================================================== */

html { scroll-behavior: smooth; background: #050505; }
body {
  font-family: var(--font-body), system-ui, sans-serif;
  color: #F5F0E8;
  background: #050505;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
.font-display { font-family: 'Fraunces', serif; font-optical-sizing: auto; }
.font-mono { font-family: 'JetBrains Mono', monospace; }

/* Massive display treatments */
.display-colossal {
  font-family: 'Fraunces', serif;
  font-weight: 300;
  letter-spacing: -0.045em;
  line-height: 0.88;
  font-variation-settings: "SOFT" 100, "WONK" 0, "opsz" 144;
}
.display-huge {
  font-family: 'Fraunces', serif;
  font-weight: 400;
  letter-spacing: -0.04em;
  line-height: 0.94;
  font-variation-settings: "SOFT" 80, "opsz" 144;
}
.display-italic {
  font-family: 'Fraunces', serif;
  font-style: italic;
  font-weight: 300;
  letter-spacing: -0.03em;
  font-variation-settings: "SOFT" 100, "opsz" 144;
}

.mono-caption {
  font-family: 'JetBrains Mono', monospace;
  font-size: 12px;
  letter-spacing: 0.18em;
  line-height: 1.5;
  text-transform: uppercase;
  font-weight: 500;
}

/* Editorial eyebrow: dash + label. Wraps the pattern repeated across pages. */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 1rem;
}
.eyebrow-line {
  display: inline-block;
  width: 2.5rem;
  height: 1px;
  background: #D4B57B;
}
.eyebrow-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 12px;
  letter-spacing: 0.18em;
  line-height: 1.5;
  text-transform: uppercase;
  font-weight: 500;
  color: #D4B57B;
}

/* Section vertical rhythm — replaces py-20/24/28/32 drift across pages. */
.section-pad { padding-top: 6rem; padding-bottom: 6rem; }
@media (min-width: 1024px) {
  .section-pad { padding-top: 8rem; padding-bottom: 8rem; }
}

/* Body copy hierarchy. Use these instead of inline text-[14/15/16/17px]. */
.body-prose { font-size: 16px; line-height: 1.7; color: #C9C3B8; }
@media (min-width: 1024px) {
  .body-prose { font-size: 17px; }
}
.body-small { font-size: 14px; line-height: 1.6; color: #C9C3B8; }

/* Ambient grain */
.grain::before {
  content: '';
  position: fixed;
  inset: 0;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.18 0'/></filter><rect width='200' height='200' filter='url(%23n)'/></svg>");
  background-size: 200px 200px;
  opacity: 0.6;
  mix-blend-mode: overlay;
  pointer-events: none;
  z-index: 9999;
}

/* ============================= */
/* TvS-style scroll choreography  */
/* All reveal animations are ADDITIVE — content shows by default, JS opts
   into the animation by adding `.is-revealing`. If JS doesn't run, content
   still shows. Bullet-proof. */
/* ============================= */
.reveal {
  will-change: opacity, transform;
}
.reveal.is-revealing {
  opacity: 0;
  transform: translateY(48px);
  transition:
    opacity 1400ms cubic-bezier(0.19, 1, 0.22, 1),
    transform 1400ms cubic-bezier(0.19, 1, 0.22, 1);
}
.reveal.is-revealing.in { opacity: 1; transform: translateY(0); }

.r-d1 { transition-delay: 140ms; }
.r-d2 { transition-delay: 280ms; }
.r-d3 { transition-delay: 420ms; }
.r-d4 { transition-delay: 560ms; }
.r-d5 { transition-delay: 700ms; }

/* LINE REVEAL — additive, opts in via `.is-revealing` so headings always
   render even if JS is delayed or fails.
   Padding gives room for ascenders (top of S, T) AND italic descenders
   (y, g, j, p) so `overflow: hidden` never clips a glyph. Negative margins
   FULLY cancel the padding so the visible vertical rhythm equals
   line-height: 1 with no residual gap.

   RULE — DO NOT BREAK:
   - padding-top  must equal -margin-top
   - padding-bottom must equal -margin-bottom
   - first-child resets margin-top to 0 (otherwise top of headline gets pulled
     under preceding eyebrow/spacing)
   - last-child resets margin-bottom to 0 (otherwise body copy after the
     headline collides with descenders)
   This is the SINGLE source of headline line-spacing for the entire site
   (every `<HeadlineLines>` and every `display-colossal` / `display-huge`
   headline). Tweaking one page's headline spacing inline (negative mt/mb,
   custom leading) is forbidden — fix it here so it stays consistent on
   mobile + desktop everywhere. */
.line {
  display: block;
  overflow: hidden;
  line-height: 1;
  padding-top: 0.18em;
  padding-bottom: 0.22em;
  /* Negative margins exceed padding by 0.06em on each side, so consecutive
     lines pull closer than line-height: 1. Net visual distance between
     baselines ≈ 0.88em — matches display-colossal's intended rhythm. */
  margin-top: -0.24em;
  margin-bottom: -0.28em;
}
.line:first-child { margin-top: -0.06em; }
.line:last-child { margin-bottom: -0.06em; }
.line > .line-inner {
  display: block;
  will-change: transform;
}
.line-group.is-revealing > .line > .line-inner {
  transform: translateY(120%);
  transition: transform 1600ms cubic-bezier(0.19, 1, 0.22, 1);
}
.line-group.is-revealing.in > .line > .line-inner,
.reveal.is-revealing.in .line > .line-inner,
.reveal.is-revealing.in.line > .line-inner { transform: translateY(0); }
.line-group.is-revealing > .line:nth-child(1) > .line-inner { transition-delay: 100ms; }
.line-group.is-revealing > .line:nth-child(2) > .line-inner { transition-delay: 260ms; }
.line-group.is-revealing > .line:nth-child(3) > .line-inner { transition-delay: 420ms; }
.line-group.is-revealing > .line:nth-child(4) > .line-inner { transition-delay: 580ms; }
.line-group.is-revealing > .line:nth-child(5) > .line-inner { transition-delay: 740ms; }

/* MEDIA REVEAL — additive, only animates when JS opts in.
   Default state shows images so anything (broken JS, slow first paint, robots,
   noscript) still sees them. JS adds `.is-revealing` to opt into the wipe,
   then `.in` to play it. */
.media {
  position: relative;
  overflow: hidden;
}
.media.is-revealing {
  clip-path: inset(100% 0 0 0);
  transition: clip-path 1800ms cubic-bezier(0.77, 0, 0.175, 1);
  will-change: clip-path;
}
.media.is-revealing > img,
.media.is-revealing > video {
  transform: scale(1.25);
  transition: transform 2200ms cubic-bezier(0.19, 1, 0.22, 1);
  will-change: transform;
}
.media.is-revealing.in { clip-path: inset(0 0 0 0); }
.media.is-revealing.in > img,
.media.is-revealing.in > video { transform: scale(1); }

/* WORD REVEAL */
.words { display: inline; }
.words .w {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
  line-height: 1.05;
}
.words .w > span {
  display: inline-block;
  transform: translateY(110%);
  transition: transform 1400ms cubic-bezier(0.19, 1, 0.22, 1);
  will-change: transform;
}
.words.in .w > span { transform: translateY(0); }
.words.in .w:nth-child(1) > span { transition-delay: 80ms; }
.words.in .w:nth-child(2) > span { transition-delay: 170ms; }
.words.in .w:nth-child(3) > span { transition-delay: 260ms; }
.words.in .w:nth-child(4) > span { transition-delay: 350ms; }
.words.in .w:nth-child(5) > span { transition-delay: 440ms; }
.words.in .w:nth-child(6) > span { transition-delay: 530ms; }
.words.in .w:nth-child(7) > span { transition-delay: 620ms; }
.words.in .w:nth-child(8) > span { transition-delay: 710ms; }

.parallax { will-change: transform; }

@media (prefers-reduced-motion: reduce) {
  .reveal, .media, .line > .line-inner, .words .w > span { opacity: 1; transform: none; clip-path: none; transition: none; }
  .media > img, .media > video { transform: none; }
}

/* Nav */
.nav-blur {
  backdrop-filter: saturate(140%) blur(20px);
  -webkit-backdrop-filter: saturate(140%) blur(20px);
  background-color: rgba(5,5,5,0.5);
  border-bottom: 1px solid rgba(255,255,255,0.06);
}

/* Active nav-link halo — soft gold glow around the label + a brighter
   underline shadow. Tuned to be visible against the void background without
   feeling neon. Applied via the `is-active` class on the link in Nav.tsx. */
.nav-link-active {
  text-shadow:
    0 0 8px rgba(212, 181, 123, 0.55),
    0 0 18px rgba(212, 181, 123, 0.30);
}
.nav-link-active::after {
  /* Empty pseudo so consumers using a `<span>` underline can layer a halo
     via box-shadow on that span. Mostly informational; the real glow on the
     underline is set on the span itself in Nav.tsx. */
  content: none;
}

/* Hover glow for inactive nav links — softer, only the text gets a faint
   halo so the layout doesn't shift. */
.nav-link-hover-glow:hover {
  text-shadow: 0 0 10px rgba(245, 240, 232, 0.28);
}

/* Buttons — base typography lives here so callsites don't need text-[Npx] tracking uppercase. */
.btn-gold,
.btn-ghost,
.btn-bone {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  transition: all 280ms cubic-bezier(0.16,1,0.3,1);
  position: relative;
  overflow: hidden;
}
.btn-gold {
  background: #D4B57B;
  color: #050505;
}
.btn-gold:hover { background: #E8D9BC; transform: translateY(-1px); box-shadow: 0 12px 40px -8px rgba(212,181,123,0.45); }
.btn-ghost {
  border: 1px solid rgba(245,240,232,0.2);
  color: #F5F0E8;
  background: transparent;
}
.btn-ghost:hover { border-color: #F5F0E8; background: rgba(245,240,232,0.04); }
.btn-bone {
  background: #F5F0E8;
  color: #050505;
}
.btn-bone:hover { background: #FFFFFF; transform: translateY(-1px); }

/* Ken Burns */
@keyframes kb { 0% { transform: scale(1.06); } 100% { transform: scale(1.18); } }
.kb { animation: kb 24s ease-in-out infinite alternate; }

/* Marquee */
@keyframes marq { from { transform: translateX(0); } to { transform: translateX(-50%); } }
.marq-track { animation: marq 22s linear infinite; }

/* Logo invert (CS logo is black on white) */
.logo-white { filter: invert(1) brightness(1.08) contrast(1.02); }

/* Cards */
.card { transition: all 500ms cubic-bezier(0.16,1,0.3,1); }
.card:hover { transform: translateY(-6px); }
.card-img { transition: transform 1200ms cubic-bezier(0.16,1,0.3,1), filter 900ms; }
.card:hover .card-img { transform: scale(1.06); }

/* Glow */
.glow {
  box-shadow:
    0 0 0 1px rgba(212,181,123,0.3),
    0 20px 60px -20px rgba(212,181,123,0.25),
    inset 0 1px 0 rgba(212,181,123,0.15);
  background:
    radial-gradient(1200px circle at 30% 0%, rgba(212,181,123,0.08) 0%, transparent 50%),
    #0A0A0A;
}

/* Hairline */
.hl { border-color: rgba(245,240,232,0.08); }

/* Hero gradient */
.hero-grad { background: linear-gradient(180deg, rgba(5,5,5,0.35) 0%, rgba(5,5,5,0.55) 55%, rgba(5,5,5,0.95) 100%); }

/* Big number outline */
.bignum {
  font-family: 'Fraunces', serif;
  font-weight: 200;
  letter-spacing: -0.08em;
  font-variation-settings: "SOFT" 100, "opsz" 144;
  line-height: 0.85;
  color: transparent;
  -webkit-text-stroke: 1px rgba(212,181,123,0.4);
}

/* Accordion */
details > summary { list-style: none; cursor: pointer; }
details > summary::-webkit-details-marker { display: none; }
details[open] .plus { transform: rotate(45deg); }
.plus { transition: transform 400ms cubic-bezier(0.16,1,0.3,1); }

/* Press logos */
.press-item { color: #8A847A; transition: color 300ms; }
.press-item:hover { color: #F5F0E8; }

/* Gold hairline */
.gold-rule { height: 1px; background: linear-gradient(90deg, transparent 0%, #D4B57B 50%, transparent 100%); }

/* Sticky CTA */
.sticky-cta { box-shadow: 0 -10px 40px rgba(0,0,0,0.6); background: rgba(10,10,10,0.9); backdrop-filter: blur(20px); }

/* Scroll indicator */
.scroll-line {
  width: 1px;
  height: 48px;
  background: linear-gradient(180deg, transparent 0%, #F5F0E8 50%, transparent 100%);
  animation: scrollPulse 2.6s ease-in-out infinite;
}
@keyframes scrollPulse {
  0%,100% { transform: translateY(0); opacity: 0.4; }
  50% { transform: translateY(8px); opacity: 1; }
}

/* =============== Hero rotator =============== */
.hero-slide { position: absolute; inset: 0; opacity: 0; transition: opacity 1600ms cubic-bezier(0.19, 1, 0.22, 1); pointer-events: none; }
.hero-slide.is-active { opacity: 1; }
.hero-slide > img, .hero-slide > video { width: 100%; height: 100%; object-fit: cover; }
.hero-slide.is-active > img, .hero-slide.is-active > video { animation: kb 26s ease-in-out infinite alternate; }

/* hero-content is TOP-anchored so the eyebrow + headline + sub + CTAs all
   start at the same Y position across rotating banners. This keeps the rotation
   visually stable instead of having content jump up/down based on length. */
.hero-content {
  position: absolute;
  inset: 0;
  opacity: 0;
  visibility: hidden;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  padding-top: 8px;
  padding-bottom: 28px; /* clear space above counter/dots */
  transition: opacity 900ms cubic-bezier(0.19, 1, 0.22, 1), visibility 0s linear 900ms;
}
.hero-content.is-active { opacity: 1; visibility: visible; transition: opacity 1100ms cubic-bezier(0.19, 1, 0.22, 1) 200ms, visibility 0s linear 0s; }

.hero-dot {
  position: relative;
  width: 36px;
  height: 2px;
  background: rgba(245, 240, 232, 0.25);
  border: none;
  cursor: pointer;
  padding: 0;
  overflow: hidden;
  transition: background 400ms;
}
.hero-dot:hover { background: rgba(245, 240, 232, 0.5); }
.hero-dot::after { content: ''; position: absolute; inset: 0; background: #D4B57B; transform: scaleX(0); transform-origin: left center; transition: transform 0s linear; }
.hero-dot.active::after { transform: scaleX(1); transition: transform var(--slide-duration, 7000ms) linear; }
.hero-dot.active { background: rgba(245, 240, 232, 0.15); }

.hero-counter { font-family: 'JetBrains Mono', monospace; font-size: 12px; letter-spacing: 0.18em; color: #C9C3B8; }
.hero-counter .num { display: inline-block; min-width: 1.4em; text-align: center; color: #D4B57B; }

/* =============== Page-specific helpers =============== */
/* VSL video frame */
.vsl-frame {
  position: relative;
  border-radius: 4px;
  overflow: hidden;
  box-shadow:
    0 0 0 1px rgba(212,181,123,0.18),
    0 60px 120px -30px rgba(0,0,0,0.7),
    0 0 240px -40px rgba(212,181,123,0.15);
}

/* Curriculum row stripe */
.row-curric { transition: background 380ms ease; }
.row-curric:hover { background: rgba(212,181,123,0.04); }

/* Portfolio masonry-style */
.masonry-col { break-inside: avoid; margin-bottom: 24px; }

/* Form input dark */
.input-dark {
  width: 100%;
  height: 52px;
  padding: 0 18px;
  background: #0A0A0A;
  border: 1px solid rgba(245,240,232,0.12);
  color: #F5F0E8;
  font-size: 14px;
  font-family: var(--font-body), sans-serif;
  transition: border-color 240ms;
}
.input-dark:focus { outline: none; border-color: #D4B57B; }
.input-dark::placeholder { color: rgba(245,240,232,0.3); }
textarea.input-dark { padding-top: 16px; height: auto; min-height: 120px; resize: vertical; }

/* =====================================================================
   .date-picker-only — public-form date inputs (type="date").
   Goal: visitor must pick a date via the calendar UI; manual typing of
   "12/02/0264" -style junk is blocked by an onKeyDown handler in the
   component. This stylesheet makes the native calendar indicator visible
   against the dark editorial theme (it ships as a near-black glyph by
   default, invisible against bg-void-soft).
   ===================================================================== */
.date-picker-only {
  /* The whole field is a click target for the picker — visitors should
     feel like they are clicking a button, not typing in a text box. */
  cursor: pointer;
  caret-color: transparent;
  /* Reserve room on the right so the indicator never overlaps the text. */
  padding-right: 48px !important;
  background-image: none;
}
.date-picker-only::-webkit-calendar-picker-indicator {
  /* Replace the near-black SVG glyph with a gold one. `filter` is the
     reliable cross-browser handle for tinting the built-in indicator. */
  filter: invert(70%) sepia(18%) saturate(632%) hue-rotate(2deg) brightness(95%) contrast(86%);
  opacity: 0.9;
  cursor: pointer;
  width: 20px;
  height: 20px;
  /* Pull the icon further into the input so it sits inside the right
     padding we reserved above. */
  margin-right: 4px;
  transition: opacity 200ms ease, transform 200ms ease;
}
.date-picker-only:hover::-webkit-calendar-picker-indicator,
.date-picker-only:focus::-webkit-calendar-picker-indicator {
  opacity: 1;
  transform: scale(1.08);
}
.date-picker-only::-webkit-datetime-edit {
  /* Keep the displayed value styled like the rest of the form copy. */
  color: #F5F0E8;
}
.date-picker-only::-webkit-datetime-edit-fields-wrapper {
  /* When typing is blocked the user still sees focus rings on segments —
     dim them so it doesn't look like an editable field. */
  opacity: 0.85;
}
.date-picker-only:disabled {
  cursor: not-allowed;
}
.date-picker-only:disabled::-webkit-calendar-picker-indicator {
  opacity: 0.25;
  cursor: not-allowed;
}

/* =====================================================================
   .jop-daypicker — themed wrapper around react-day-picker.
   We override the library's CSS custom properties + a handful of class
   selectors to fit the dark editorial theme. Tokens (gold, bone, void)
   match the values in tailwind.config.ts so the picker looks like it
   was hand-built for this site, not bolted on.
   ===================================================================== */
.jop-daypicker {
  /* react-day-picker v9 exposes design tokens via CSS variables —
     overriding them here is the cleanest theming path. */
  --rdp-accent-color: #D4B57B;              /* gold */
  --rdp-accent-background-color: rgba(212, 181, 123, 0.16);
  --rdp-background-color: transparent;
  --rdp-day-height: 36px;
  --rdp-day-width: 36px;
  --rdp-day_button-height: 36px;
  --rdp-day_button-width: 36px;
  --rdp-day_button-border-radius: 4px;
  --rdp-selected-border: 1px solid #D4B57B;
  --rdp-today-color: #D4B57B;
  --rdp-range_middle-color: #F5F0E8;
  --rdp-range_middle-background-color: rgba(212, 181, 123, 0.10);
  --rdp-disabled-opacity: 0.25;
  --rdp-outside-opacity: 0.3;
  --rdp-weekday-padding: 0.5rem 0;
  --rdp-weekday-text-transform: uppercase;

  /* Layout shell — sits inside the bg-void-card popover already painted
     by the React component. The library renders its grid inside; we
     just polish the padding and typography. */
  padding: 12px 14px 14px;
  color: #F5F0E8;
  font-family: var(--font-body, ui-sans-serif), sans-serif;
  font-size: 14px;
  min-width: 280px;
}
/* Month/year header above the grid. */
.jop-daypicker .rdp-month_caption,
.jop-daypicker .rdp-caption_label {
  font-family: var(--font-display, Georgia), serif;
  font-size: 15px;
  letter-spacing: 0;
  color: #F5F0E8;
  text-transform: none;
  padding: 4px 0 10px;
}
/* Nav chevrons (prev/next month). */
.jop-daypicker .rdp-nav button,
.jop-daypicker .rdp-button_previous,
.jop-daypicker .rdp-button_next {
  background: transparent;
  border: 1px solid rgba(245, 240, 232, 0.12);
  color: #F5F0E8;
  width: 28px;
  height: 28px;
  border-radius: 4px;
  transition: border-color 200ms, color 200ms;
}
.jop-daypicker .rdp-nav button:hover,
.jop-daypicker .rdp-button_previous:hover,
.jop-daypicker .rdp-button_next:hover {
  border-color: #D4B57B;
  color: #D4B57B;
}
.jop-daypicker .rdp-nav button:disabled,
.jop-daypicker .rdp-button_previous:disabled,
.jop-daypicker .rdp-button_next:disabled {
  opacity: 0.25;
  cursor: not-allowed;
}
/* Weekday labels (Mon Tue Wed ...) — JetBrains Mono caption styling. */
.jop-daypicker .rdp-weekday,
.jop-daypicker .rdp-head_cell {
  font-family: var(--font-mono, ui-monospace), monospace;
  font-size: 10px;
  letter-spacing: 0.18em;
  color: #8A847A;
  text-transform: uppercase;
  font-weight: 400;
}
/* Day cells. */
.jop-daypicker .rdp-day {
  font-family: var(--font-mono, ui-monospace), monospace;
  font-size: 13px;
}
.jop-daypicker .rdp-day_button {
  color: #F5F0E8;
  background: transparent;
  border: 1px solid transparent;
  transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease;
}
.jop-daypicker .rdp-day_button:hover {
  background: rgba(212, 181, 123, 0.10);
  border-color: rgba(212, 181, 123, 0.4);
  color: #D4B57B;
}
/* Today — gold ring instead of full fill so the selection state stays distinct. */
.jop-daypicker .rdp-today .rdp-day_button {
  color: #D4B57B;
  font-weight: 500;
}
.jop-daypicker .rdp-selected .rdp-day_button,
.jop-daypicker .rdp-day_selected .rdp-day_button {
  background: #D4B57B !important;
  color: #0A0A0A !important;
  border-color: #D4B57B !important;
  font-weight: 500;
}
.jop-daypicker .rdp-disabled .rdp-day_button,
.jop-daypicker .rdp-day_disabled .rdp-day_button {
  color: rgba(245, 240, 232, 0.25);
  cursor: not-allowed;
}
.jop-daypicker .rdp-outside .rdp-day_button,
.jop-daypicker .rdp-day_outside .rdp-day_button {
  color: rgba(245, 240, 232, 0.30);
}
/* Hide the library's footer area; we manage close on selection from JS. */
.jop-daypicker .rdp-footer {
  display: none;
}
