/* site.css

   Everything written by hand. Loads after normalize / webflow /
   bachner-demo, which are untouched Webflow output — if it's in here it's
   mine, if it's in there it's generated.

   1 tokens   2 reveals   3 hover   4 focus   5 nav   6 background
   7 forms    8 fixes     9 modals  10 disclosures    11 real buttons */


/* 1. Tokens ------------------------------------------------------------ */

:root {
  --ink:        #0f2b41;   /* page navy */
  --bone:       #fefcfb;   /* off-white text */
  --sky:        #91bad5;   /* light blue accent */
  --blue:       #3b809f;   /* button blue */

  /* One rise, one duration, one curve, one stagger step. Every reveal uses
     these four. */
  --rise:       16px;
  --dur:        500ms;
  --ease:       cubic-bezier(.22, .61, .36, 1);   /* ease-out */
  --stagger:    70ms;

  /* Interaction is faster than reveals on purpose — a 500ms hover feels
     broken. */
  --ui-dur:     180ms;
  --ui-ease:    cubic-bezier(.33, 1, .68, 1);
}


/* 2. Reveals ------------------------------------------------------------

   Nothing is hidden by default. `motion` on <html> comes from the inline
   script in each <head>, only when JS runs and reduced-motion is off. No
   class, no match, everything visible. That's the fallback.

   Webflow did the reverse: style="opacity:0" baked onto 41 elements, so JS
   off meant a blank page.                                                */

.motion [data-reveal] {
  opacity: 0;
  transform: translateY(var(--rise));
  transition: opacity   var(--dur) var(--ease),
              transform var(--dur) var(--ease);
  /* set by site.js on staggered children; defaults to 0 */
  transition-delay: calc(var(--reveal-index, 0) * var(--stagger));
}

.motion [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
}

/* These beat the hover transitions in section 3, so site.js strips
   data-reveal once an element lands — otherwise a revealed button's hover
   lift also runs at 500ms. Don't lengthen these selectors without
   rechecking that. */

/* Photos rise a little further and settle from .985. Small on purpose —
   at .95 it reads as a zoom, which is what we're avoiding. */
.motion [data-reveal="media"] {
  transform: translateY(24px) scale(.985);
}

.motion [data-reveal="media"].is-revealed {
  transform: none;
}

/* Second line of defence — the head script already withholds `motion`.
   This catches the preference being switched on after load, and flattens
   the interaction transitions below too. */
@media (prefers-reduced-motion: reduce) {
  .motion [data-reveal],
  .motion [data-reveal="media"] {
    opacity: 1;
    transform: none;
    transition: none;
  }

  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}


/* 3. Hover / active -----------------------------------------------------
   The export had 41 scroll animations and not one transition, so every
   hover state changed instantly.                                        */

.button,
.submit-button,
.footer-link,
.social,
.nav-link,
.text-field,
.text-field-2,
.text-field-3,
.w-input {
  transition: background-color var(--ui-dur) var(--ui-ease),
              border-color     var(--ui-dur) var(--ui-ease),
              color            var(--ui-dur) var(--ui-ease),
              opacity          var(--ui-dur) var(--ui-ease),
              transform        var(--ui-dur) var(--ui-ease),
              box-shadow       var(--ui-dur) var(--ui-ease);
}

/* 2px is the smallest lift that still registers. Back to 0 on :active so
   a click feels like a press. */
.button:hover,
.submit-button:hover {
  transform: translateY(-2px);
}

.button:active,
.submit-button:active {
  transform: translateY(0);
}

.footer-link:hover {
  transform: translateX(3px);
}


/* 4. Focus --------------------------------------------------------------
   The export had no :focus or outline rule at all. :focus-visible is
   keyboard-only, so a mouse click never draws a ring.                   */

:focus-visible {
  outline: 3px solid var(--sky);
  outline-offset: 3px;
  border-radius: 3px;
}

.button:focus-visible,
.submit-button:focus-visible {
  outline-offset: 4px;
}

/* first in the tab order, offscreen until focused */
.skip-link {
  position: absolute;
  left: 16px;
  top: -100px;
  z-index: 999;
  padding: 12px 20px;
  border-radius: 8px;
  background: var(--bone);
  color: var(--ink);
  font-weight: 500;
  text-decoration: none;
  transition: top var(--ui-dur) var(--ui-ease);
}

.skip-link:focus {
  top: 16px;
  color: var(--ink);
}


/* Header band.

   The navbar moved out of `.section.top` into its own <header> and lost
   that section's margins. This puts it back on the site's own ladder:

     >= 992    60 top / 60 sides
     <= 991    40 / 60
     <= 767    20 / 40
     <= 479    20 / 20

   Sides matter as much as top. .container-nav is a centred 1320px box with
   no padding of its own, so without these the navbar runs flush to the
   viewport edges below ~1440px — it only looked right at exactly 1440. */

.site-header {
  padding: 60px 60px 0;
}

@media screen and (max-width: 991px) {
  .site-header { padding: 40px 60px 0; }
}

@media screen and (max-width: 767px) {
  .site-header { padding: 20px 40px 0; }
}

@media screen and (max-width: 479px) {
  .site-header { padding: 20px 20px 0; }
}

/* Content starts one step below the band, matching the step above it.

   Load-bearing, not cosmetic: .books-sec, .event-sec and the bare
   .header-div on appearances have no top margin of their own. They used to
   sit under an empty .section.top that supplied it, and that went away with
   the navbar. Without this their headings sit 16px under the nav.       */

main > :first-child {
  margin-top: 60px;
}

@media screen and (max-width: 991px) {
  main > :first-child { margin-top: 40px; }
}

@media screen and (max-width: 767px) {
  main > :first-child { margin-top: 20px; }
}

/* 5. Navigation ---------------------------------------------------------
   Renamed from .cleaning-nav-* (inherited from an unrelated template). The
   old markup shipped 35 .cleaning-nav-underline divs that were display:none
   on every breakpoint; this does it with one pseudo-element.            */

.nav-item {
  position: relative;
}

.nav-link {
  position: relative;
  display: inline-block;
  padding-bottom: 10px;
  color: #fefcfbcc;
  letter-spacing: .25px;
  text-decoration: none;
}

.nav-link:hover {
  color: var(--bone);
}

.nav-link::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--ui-dur) var(--ease);
}

.nav-link:hover::after,
.nav-link:focus-visible::after {
  transform: scaleX(1);
}

/* current page keeps its underline up, in the accent */
.nav-link.w--current {
  color: var(--bone);
}

.nav-link.w--current::after {
  background: var(--sky);
  transform: scaleX(1);
}

/* Mark plus live text, replacing the 10.9KB outlined wordmark. Selectable,
   scales with the user's font size, read correctly by screen readers. */
.brand {
  display: flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
  color: var(--bone);
}

.brand-mark {
  height: 22px;
  width: auto;
  color: var(--sky);
  flex: none;
  transition: transform var(--ui-dur) var(--ui-ease);
}

.brand:hover .brand-mark {
  transform: translateY(-2px) rotate(-4deg);
}

.brand-name {
  font-size: 1.15rem;
  font-weight: 500;
  letter-spacing: .04em;
  text-transform: uppercase;
  white-space: nowrap;
}

.brand-name .brand-name-title {
  font-weight: 200;
}

/* Mobile menu. Webflow built this at runtime, so dropping webflow.js means
   owning it. Hidden with the `hidden` attribute rather than a class, which
   keeps it out of the accessibility tree as well as out of sight. */
.menu-button {
  background: none;
  border: 0;
  padding: 14px;
  color: var(--bone);
  cursor: pointer;
  line-height: 0;
}

.menu-button .menu-icon {
  display: block;
  width: 26px;
  height: 2px;
  background: currentColor;
  position: relative;
  transition: background-color var(--ui-dur) var(--ui-ease);
}

.menu-button .menu-icon::before,
.menu-button .menu-icon::after {
  content: "";
  position: absolute;
  left: 0;
  width: 26px;
  height: 2px;
  background: currentColor;
  transition: transform var(--ui-dur) var(--ui-ease);
}

.menu-button .menu-icon::before { top: -8px; }
.menu-button .menu-icon::after  { top:  8px; }

/* bars cross into an X */
.menu-button[aria-expanded="true"] .menu-icon {
  background: transparent;
}

.menu-button[aria-expanded="true"] .menu-icon::before {
  transform: translateY(8px) rotate(45deg);
}

.menu-button[aria-expanded="true"] .menu-icon::after {
  transform: translateY(-8px) rotate(-45deg);
}

/* Menu visibility lives here, not in Webflow — data-collapse came off the
   navbar with webflow.js, so its rules no longer apply.

   All of it gated on `.js`. Without JS the toggle would be inert, so it
   stays hidden and the menu renders as a plain stacked list instead. */

.menu-button {
  display: none;
}

@media screen and (max-width: 991px) {
  .js .menu-button {
    display: block;
  }

  .js .nav-menu {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 50;
    background: #3f6695;
    border-radius: 0 0 16px 16px;
    box-shadow: 0 18px 40px rgba(4, 16, 26, .45);
  }

  .nav-link {
    display: block;
    width: 100%;
    padding: 14px 6px;
  }

  /* full-width underline on a stacked row reads as a divider, so colour
     carries the state here instead */
  .nav-link::after {
    display: none;
  }

  .nav-link.w--current {
    color: var(--sky);
  }

  /* No JS: the toggle is hidden, so the menu has to stand on its own.
     Webflow floats it right, which parks it beside the brand — these drop
     it onto its own full-width row. */
  html:not(.js) .container-nav {
    flex-wrap: wrap;
  }

  html:not(.js) .nav-menu {
    float: none;
    width: 100%;
    margin-top: 12px;
    background: #3f6695;
    border-radius: 12px;
  }

  html:not(.js) .animated-nav-menu {
    padding: 6px 10px;
  }
}


/* Webflow sets .brand.w--current { display: block } below 768px, left over
   from when the brand was one <img>. The lockup is a flex row now, so that
   kills the gap and the mark collides with the name — homepage only, since
   it's the only page where the brand is the current link. Same specificity
   as Webflow's rule (0,2,0) and this file loads later, so it wins. */

@media screen and (max-width: 767px) {
  .brand.w--current {
    display: flex;
  }
}

/* 6. Background ---------------------------------------------------------
   <body class="body bg-wash">. One fixed composited layer rather than
   background-attachment: fixed on the body, which repaints every scroll
   frame and janks on phones.                                            */

.bg-wash {
  background-color: var(--ink);
  position: relative;
}

.bg-wash::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  background:
    /* vignette — darkens the edges so the centre column lifts */
    radial-gradient(130% 100% at 50% 38%,
      rgba(8, 24, 38, 0) 42%, rgba(8, 24, 38, .62) 100%),
    /* four washes, no two centres near each other */
    radial-gradient(58vw 58vw at 12%  6%,  rgba(64, 128, 172, .30), transparent 62%),
    radial-gradient(52vw 52vw at 88% 30%,  rgba(38,  82, 126, .30), transparent 60%),
    radial-gradient(68vw 68vw at 26% 74%,  rgba(24,  74, 112, .34), transparent 64%),
    radial-gradient(46vw 46vw at 86% 96%,  rgba(80, 122, 162, .20), transparent 58%);
}

/* Faint grain on top. Flat navy plus smooth gradients bands on 8-bit
   displays; grain breaks it up, and it's what stops the page reading as
   empty dark blue. Inline SVG, so no request. */
.bg-wash::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: .30;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='.36'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

/* 7. Forms --------------------------------------------------------------
   site.js sets aria-invalid and moves focus; this only makes that visible. */

.field-row {
  margin-bottom: 4px;
}

.w-input:focus,
.text-field:focus,
.text-field-2:focus,
.text-field-3:focus {
  border-color: var(--sky);
  box-shadow: 0 0 0 3px rgba(145, 186, 213, .28);
  outline: none;
}

[aria-invalid="true"] {
  border-color: #ffb3a7 !important;
  box-shadow: 0 0 0 3px rgba(255, 179, 167, .25);
}

.field-error {
  display: block;
  min-height: 0;
  margin-top: 4px;
  color: #ffc9c0;
  font-size: .85rem;
  line-height: 1.2rem;
}

/* replaces the form once it validates */
.form-reply {
  padding: 28px;
  border: 1px solid rgba(254, 252, 251, .25);
  border-radius: 16px;
  background: rgba(255, 255, 255, .04);
}

.form-reply h3 {
  margin-top: 0;
  font-size: 1.5rem;
  font-weight: 500;
  line-height: 1.8rem;
}

.form-reply p {
  color: #fefcfbcc;
  font-weight: 200;
  line-height: 1.6rem;
}

.form-reply p:last-child {
  margin-bottom: 0;
}

.form-note {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid rgba(254, 252, 251, .18);
  color: #fefcfb99;
  font-size: .8rem;
  line-height: 1.15rem;
}


/* 8. Fixes --------------------------------------------------------------
   Patches to inherited styles, kept here so the generated files stay clean. */

/* Subhead is 19.2px regular over a photo — measured 4.5:1, exactly on the
   AA line for body text and under it wherever the card runs lighter. The
   shadow darkens only what sits directly behind the glyphs. */
.subhead {
  font-weight: 500;
  text-shadow: 0 1px 10px rgba(8, 24, 38, .75);
}

.heading,
.div-block-2 .heading {
  text-shadow: 0 2px 18px rgba(8, 24, 38, .45);
}

/* several are scaled well below their natural size */
.image-5,
.image-6,
.image-10,
.image-11 {
  height: auto;
}


/* Hidden but still announced, for labels the layout has no room to show.
   Not display:none — that would hide them from screen readers too. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}


/* 9. Modals -------------------------------------------------------------
   Native <dialog>. It supplies the backdrop, Escape and focus handling;
   everything here is appearance only.                                   */

.modal {
  max-width: 34rem;
  padding: 0;
  border: 1px solid rgba(254, 252, 251, .28);
  border-radius: 18px;
  background: #16344c;
  color: var(--bone);
  box-shadow: 0 30px 80px rgba(4, 16, 26, .55);
}

.modal.modal-wide {
  max-width: 40rem;
}

.modal::backdrop {
  background: rgba(6, 18, 29, .72);
}

.modal-body {
  padding: 34px;
}

.modal-text {
  margin: 0 0 24px;
  font-size: 1.25rem;
  font-weight: 200;
  line-height: 1.85rem;
}

/* .button centres itself with auto side margins; restated so it reads as
   deliberate rather than inherited. */
.modal-close {
  margin-left: auto;
  margin-right: auto;
}

/* Animates only when motion is welcome. allow-discrete lets display take
   part in the transition, so it fades instead of popping. */
.motion .modal {
  opacity: 0;
  transform: translateY(8px) scale(.98);
  transition: opacity 220ms var(--ease),
              transform 220ms var(--ease),
              overlay 220ms allow-discrete,
              display 220ms allow-discrete;
}

.motion .modal[open] {
  opacity: 1;
  transform: none;
}

@starting-style {
  .motion .modal[open] {
    opacity: 0;
    transform: translateY(8px) scale(.98);
  }
}


/* 10. Disclosures -------------------------------------------------------
   The Topics article lists.                                             */

.drop-title {
  width: 100%;
  padding-bottom: 0;
}

.dropper-link {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 12px 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.dropper-link:hover {
  color: var(--sky);
}

/* arrow.svg points right: at the heading when closed, down over the list
   it has opened. */
.drop-arrow {
  width: 32px;
  height: auto;
  flex: none;
  transition: transform var(--ui-dur) var(--ease);
}

[aria-expanded="true"] .drop-arrow {
  transform: rotate(90deg);
}

.dropped-div .list {
  margin-top: 0;
  padding-left: 42px;
}

.list-item .link {
  color: #fefcfbcc;
  text-decoration: underline;
  text-underline-offset: 2px;
  transition: color var(--ui-dur) var(--ui-ease);
}

.list-item .link:hover {
  color: var(--bone);
}


/* 11. Real buttons ------------------------------------------------------
   .button and .social were written for <a>. The modal triggers are actual
   <button>s, so the UA's own button styling has to be cleared.          */

button.button,
button.social {
  border: 0;
  font: inherit;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

button.button {
  color: var(--bone);
  font-weight: 500;
  letter-spacing: inherit;
}

button.button.ghost {
  border: 1px solid var(--bone);
}

button.social {
  padding: 0;
  background: none;
  line-height: 0;
}

/* Nothing behind these but a dialog, so without JS they'd be dead. Hiding
   is honest; a button that silently does nothing is the thing to avoid. */
html:not(.js) [data-modal-open] {
  display: none;
}

/* Covers trigger the buy modal, so they're buttons. Strip the chrome and
   let the image do the work. */
button.cover-button {
  display: block;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  line-height: 0;
  cursor: pointer;
  transition: transform var(--ui-dur) var(--ui-ease);
}

button.cover-button:hover {
  transform: translateY(-3px);
}

button.cover-button:focus-visible {
  outline: 3px solid var(--sky);
  outline-offset: 5px;
}
