/* Threshold Social — brand layer on top of Tailwind
   Tokens derive from the brand guide. Sage is accent only.

   CONTRAST RULE (learned the hard way):
   Sand (#F1E2D0) on Paper is 1.18:1 — invisible.
   Sage (#9BCBB2) on any light surface is <1.7:1 — invisible.
   Therefore: light-surface controls are ALWAYS ink-on-light.
   Sand/sage text is ONLY ever used inside .on-dark.
   Every rule below is scoped so a control cannot inherit the wrong one. */

:root {
  --ink: #4B2509;
  --ink-deep: #331903;
  --bark: #7D6240;
  --sand: #F1E2D0;
  --sand-warm: #EAD9C4;
  --paper: #FBF7F1; /* warmed from #F6F6F6 — pure grey read cold and empty */
  --sage: #9BCBB2;
  --sage-ink: #3E6B54; /* dark sage: legible on light surfaces */
  --thread: rgba(75, 37, 9, 0.16);
  --thread-soft: rgba(75, 37, 9, 0.09);
}

html {
  scroll-behavior: smooth;
}

body {
  background: var(--paper);
  color: var(--ink);
  font-family: "DM Sans", ui-sans-serif, system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

::selection {
  background: var(--sage);
  color: var(--ink-deep);
}

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

.on-dark :focus-visible {
  outline-color: var(--sage);
}

/* ---- Surfaces ----------------------------------------------------------
   Alternating warmth so no section reads as blank white. */

.surface-paper {
  background: var(--paper);
}

.surface-sand {
  background: var(--sand);
}

.surface-warm {
  background: linear-gradient(180deg, var(--paper) 0%, var(--sand-warm) 100%);
}

.surface-dark {
  background: var(--ink);
}

.surface-dark-deep {
  background: radial-gradient(
      120% 90% at 78% 8%,
      rgba(125, 98, 64, 0.42) 0%,
      rgba(125, 98, 64, 0) 62%
    ),
    var(--ink-deep);
}

/* Paper grain — a faint tooth so large light areas have material,
   not emptiness.

   NOTE: this was originally a ::before with `isolation:isolate` + `z-index:-1`.
   That was a bug. `isolation:isolate` creates a stacking context, and inside one
   a negative z-index child CANNOT paint behind its own parent's background — so
   the noise composited ON TOP of the surface. With `mix-blend-mode:overlay` it
   washed dark browns out to near-white while sand text stayed sand, giving
   ~1.4:1 text. Grain is now an extra background-image layer on the element
   itself, which paints above the element's background-color and below all
   content, with no stacking context and no blend mode. */

.grain {
  position: relative;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.045'/%3E%3C/svg%3E");
  background-repeat: repeat;
}

/* Surfaces re-declare their own paint UNDER the grain layer. Order matters:
   the grain image is listed first, the color/gradient last. */
.surface-paper.grain {
  background-color: var(--paper);
}

.surface-sand.grain {
  background-color: var(--sand);
}

.surface-warm.grain {
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.045'/%3E%3C/svg%3E"),
    linear-gradient(180deg, var(--paper) 0%, var(--sand-warm) 100%);
}

.surface-dark.grain {
  background-color: var(--ink);
}

.surface-dark-deep.grain {
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E"),
    radial-gradient(
      120% 90% at 78% 8%,
      rgba(125, 98, 64, 0.42) 0%,
      rgba(125, 98, 64, 0) 62%
    );
  background-color: var(--ink-deep);
}

/* Arch watermark — the doorway, huge and barely there. Fills dead space
   with brand rather than decoration. */
.watermark {
  position: absolute;
  pointer-events: none;
  z-index: 0;
  border: 1px solid var(--thread-soft);
  border-radius: 999px 999px 0 0;
  border-bottom: 0;
}

.on-dark .watermark {
  border-color: rgba(241, 226, 208, 0.09);
}

/* ---- Type ------------------------------------------------------------- */

.font-display {
  font-family: "Libre Baskerville", ui-serif, Georgia, serif;
}

.font-script {
  font-family: "Allura", cursive;
}

.h-hero {
  font-family: "Libre Baskerville", ui-serif, Georgia, serif;
  font-weight: 400;
  line-height: 1.02;
  letter-spacing: -0.03em;
}

.h-sect {
  font-family: "Libre Baskerville", ui-serif, Georgia, serif;
  font-weight: 400;
  line-height: 1.12;
  letter-spacing: -0.02em;
}

.eyebrow {
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
}

/* Eyebrow with a rule running off it — anchors a heading to the grid */
.eyebrow-rule {
  display: flex;
  align-items: center;
  gap: 0.9rem;
}

.eyebrow-rule::after {
  content: "";
  height: 1px;
  flex: 1;
  background: var(--thread);
}

.on-dark .eyebrow-rule::after {
  background: rgba(241, 226, 208, 0.22);
}

.lede {
  font-size: clamp(1.0625rem, 1.6vw, 1.25rem);
  line-height: 1.62;
}

/* Drop cap for the opening paragraph of a story block */
.dropcap::first-letter {
  float: left;
  font-family: "Libre Baskerville", ui-serif, Georgia, serif;
  font-size: 3.6em;
  line-height: 0.82;
  padding: 0.06em 0.12em 0 0;
  color: var(--ink);
}

/* ---- Signature: the threshold line ------------------------------------
   A hairline that runs the page and opens into an arch at each crossing. */

.thresh {
  position: relative;
  display: block;
  height: 68px;
  overflow: hidden;
}

.thresh::before,
.thresh::after {
  content: "";
  position: absolute;
  top: 50%;
  height: 1px;
  background: var(--thread);
}

.thresh::before {
  left: 0;
  right: calc(50% + 34px);
}

.thresh::after {
  left: calc(50% + 34px);
  right: 0;
}

.thresh-arch {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 68px;
  height: 34px;
  transform: translate(-50%, -100%);
  border: 1px solid var(--thread);
  border-bottom: 0;
  border-radius: 34px 34px 0 0;
}

.thresh-arch::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -3px;
  width: 5px;
  height: 5px;
  transform: translateX(-50%) rotate(45deg);
  background: var(--sage-ink);
}

.on-dark .thresh::before,
.on-dark .thresh::after {
  background: rgba(241, 226, 208, 0.22);
}

.on-dark .thresh-arch {
  border-color: rgba(241, 226, 208, 0.22);
}

.on-dark .thresh-arch::after {
  background: var(--sage);
}

/* ---- Arch masks (echoes the logo's doorway) ---------------------------- */

.arch {
  border-radius: 999px 999px 6px 6px;
  overflow: hidden;
}

.arch-soft {
  border-radius: 160px 160px 6px 6px;
  overflow: hidden;
}

/* Framed arch: a hairline offset behind the image, like a door in a wall */
.arch-framed {
  position: relative;
}

.arch-framed::before {
  content: "";
  position: absolute;
  inset: -14px -14px -14px 14px;
  border: 1px solid var(--thread);
  border-radius: 160px 160px 6px 6px;
  pointer-events: none;
}

.on-dark .arch-framed::before {
  border-color: rgba(241, 226, 208, 0.2);
}

/* ---- Controls ----------------------------------------------------------
   Scoped so light and dark can never bleed into each other. */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  padding: 0.9rem 1.6rem;
  border-radius: 999px;
  font-size: 0.9375rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  border: 1px solid transparent;
  transition: transform 180ms ease, background-color 180ms ease,
    color 180ms ease, border-color 180ms ease, box-shadow 180ms ease;
  will-change: transform;
}

.btn:hover {
  transform: translateY(-2px);
}

.btn:active {
  transform: translateY(0);
}

/* --- Light surfaces: ink on light. 12.4:1. --- */
.btn-solid {
  background: var(--ink);
  color: var(--sand);
  border-color: var(--ink);
  box-shadow: 0 1px 2px rgba(75, 37, 9, 0.14);
}

.btn-solid:hover {
  background: var(--ink-deep);
  border-color: var(--ink-deep);
  box-shadow: 0 8px 20px -8px rgba(75, 37, 9, 0.5);
}

.btn-ghost {
  background: transparent;
  border-color: rgba(75, 37, 9, 0.28);
  color: var(--ink); /* explicit — this was the invisible-button bug */
}

.btn-ghost:hover {
  border-color: var(--ink);
  background: rgba(75, 37, 9, 0.04);
}

/* --- Dark surfaces: sand on ink. 10.5:1. --- */
.on-dark .btn-solid {
  background: var(--sand);
  color: var(--ink-deep);
  border-color: var(--sand);
  box-shadow: none;
}

.on-dark .btn-solid:hover {
  background: #fff;
  border-color: #fff;
  box-shadow: 0 8px 24px -8px rgba(0, 0, 0, 0.55);
}

.on-dark .btn-ghost {
  background: transparent;
  border-color: rgba(241, 226, 208, 0.45);
  color: var(--sand);
}

.on-dark .btn-ghost:hover {
  border-color: var(--sand);
  background: rgba(241, 226, 208, 0.08);
}

/* Link underline that opens like a door on hover */
.link-door {
  position: relative;
  display: inline-block;
}

.link-door::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -3px;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 260ms cubic-bezier(0.2, 0.7, 0.3, 1);
}

.link-door:hover::after,
.link-door[aria-current="page"]::after {
  transform: scaleX(1);
}

/* ---- Cards ------------------------------------------------------------- */

.card {
  position: relative;
  background: var(--paper);
  border: 1px solid var(--thread-soft);
  border-radius: 3px;
  transition: border-color 240ms ease, transform 240ms ease,
    box-shadow 240ms ease;
}

.card:hover {
  border-color: var(--thread);
  transform: translateY(-3px);
  box-shadow: 0 18px 40px -22px rgba(75, 37, 9, 0.35);
}

/* Marked card: small arch sits on the top edge, like a door in the frame */
.card-arch::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 2rem;
  width: 34px;
  height: 17px;
  background: var(--paper);
  border: 1px solid var(--thread-soft);
  border-bottom: 0;
  border-radius: 17px 17px 0 0;
  transform: translateY(-100%);
}

.on-dark .card {
  background: rgba(241, 226, 208, 0.04);
  border-color: rgba(241, 226, 208, 0.14);
}

.on-dark .card:hover {
  border-color: rgba(241, 226, 208, 0.3);
  box-shadow: 0 18px 40px -22px rgba(0, 0, 0, 0.6);
}

/* ---- Stat / metric ------------------------------------------------------
   Upper bound kept modest: these sit in a flex row of 3, and a long word like
   "Richmond, VA" at 2.5rem overflowed its column and collided with the next
   stat. Sized to fit the content, not to shout. */

.stat-value {
  font-family: "Libre Baskerville", ui-serif, Georgia, serif;
  font-size: clamp(1.15rem, 1.7vw, 1.5rem);
  line-height: 1.15;
  letter-spacing: -0.01em;
}

/* ---- Fields ------------------------------------------------------------ */

.field {
  width: 100%;
  background: rgba(255, 255, 255, 0.5);
  border: 1px solid var(--thread-soft);
  border-radius: 3px;
  padding: 0.8rem 0.95rem;
  font-size: 1rem;
  color: var(--ink);
  transition: border-color 200ms ease, background-color 200ms ease;
}

.field::placeholder {
  color: rgba(75, 37, 9, 0.38);
}

.field:focus {
  outline: none;
  border-color: var(--ink);
  background: #fff;
}

.field:focus-visible {
  outline: none;
}

.field-label {
  display: block;
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(75, 37, 9, 0.62);
  margin-bottom: 0.45rem;
}

/* ---- Disclosure -------------------------------------------------------- */

.disc {
  border-bottom: 1px solid var(--thread);
}

.on-dark .disc {
  border-bottom-color: rgba(241, 226, 208, 0.2);
}

.disc summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 1.15rem 0;
  font-size: 1rem;
  transition: color 200ms ease, padding-left 240ms ease;
}

.disc summary:hover {
  padding-left: 0.4rem;
}

.disc summary::-webkit-details-marker {
  display: none;
}

.disc .disc-sign {
  position: relative;
  flex: none;
  width: 13px;
  height: 13px;
}

.disc .disc-sign::before,
.disc .disc-sign::after {
  content: "";
  position: absolute;
  background: currentColor;
  transition: transform 240ms cubic-bezier(0.2, 0.7, 0.3, 1);
}

.disc .disc-sign::before {
  top: 6px;
  left: 0;
  width: 13px;
  height: 1px;
}

.disc .disc-sign::after {
  left: 6px;
  top: 0;
  width: 1px;
  height: 13px;
}

.disc[open] .disc-sign::after {
  transform: scaleY(0);
}

.disc-body {
  padding-bottom: 1.25rem;
  font-size: 0.9375rem;
  line-height: 1.65;
  color: rgba(75, 37, 9, 0.78);
  max-width: 62ch;
}

.on-dark .disc-body {
  color: rgba(241, 226, 208, 0.78);
}

/* ---- Marks -------------------------------------------------------------- */

/* Small door glyph used as a list marker */
.door-mark {
  flex: none;
  width: 11px;
  height: 14px;
  border: 1px solid var(--sage-ink);
  border-right-width: 3px;
  border-radius: 5px 5px 0 0;
  opacity: 0.85;
}

.on-dark .door-mark {
  border-color: var(--sage);
}

/* ---- Scroll reveal ----------------------------------------------------- */

/* ---- Scroll reveal -----------------------------------------------------
   PROGRESSIVE ENHANCEMENT — content is visible by DEFAULT.

   This previously set `.reveal { opacity: 0 }` unconditionally and relied on
   JS (IntersectionObserver) to add `.is-in`. That made 17 elements per page
   invisible until JS ran, which meant:
     - no JS / JS error / slow JS  -> permanently invisible text
     - full-page screenshot tools  -> capture mid-fade, text looks washed out
     - some in-app + reader browsers -> blank sections
   Text must never depend on JS to be readable.

   Now: the hiding rules only apply when JS has confirmed it is running by
   setting `.js` on <html>. Without JS, everything renders fully opaque. */

.reveal {
  opacity: 1;
  transform: none;
}

.js .reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 720ms cubic-bezier(0.2, 0.7, 0.3, 1),
    transform 720ms cubic-bezier(0.2, 0.7, 0.3, 1);
}

.js .reveal.is-in {
  opacity: 1;
  transform: none;
}

@keyframes door-open {
  from {
    clip-path: inset(0 50% 0 50%);
  }
  to {
    clip-path: inset(0 0 0 0);
  }
}

.js .door-open {
  animation: door-open 1100ms cubic-bezier(0.2, 0.7, 0.3, 1) both;
}

@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(14px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* `both` fill-mode holds opacity:0 during animation-delay, so this is also
   gated behind .js — otherwise delayed hero text is invisible without JS. */
.js .rise {
  animation: rise 780ms cubic-bezier(0.2, 0.7, 0.3, 1) both;
}

.js .rise-1 { animation-delay: 120ms; }
.js .rise-2 { animation-delay: 260ms; }
.js .rise-3 { animation-delay: 400ms; }
.js .rise-4 { animation-delay: 540ms; }

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-delay: 0ms !important;
    transition-duration: 0.001ms !important;
  }
  .reveal,
  .js .reveal {
    opacity: 1;
    transform: none;
  }
  .btn:hover,
  .card:hover {
    transform: none;
  }
}

/* Print / capture: never hide content. */
@media print {
  .reveal,
  .js .reveal,
  .js .rise,
  .js .door-open {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
    clip-path: none !important;
  }
}
