/* discipleshiptogether.com
 *
 * One stylesheet, no build step, no JavaScript.
 *
 * ── The palette has no hue, and that is the design ──────────────────────
 *
 * Thread's accent is violet (~249deg) and Emberside's is clay (~24deg) with a
 * sage second (~85deg). Split those the short way and you land on green, which
 * collides with sage; split them the long way and you land near magenta, which
 * fights both. Any third hue here is either a warmer Thread or a cooler
 * Emberside, so the joint chrome carries no colour at all: graphite and paper.
 *
 * The only colour on a page is a product's own, and it is fenced inside that
 * product's block. A joint site that adopts a third brand colour becomes a
 * third brand.
 *
 * The darks carry a faint cool cast (#1b1c20 is R27 G28 B32) so they are not
 * Emberside's warm night #1a1816, while staying well short of Thread's
 * blue-violet #161826. Neither product can claim the chrome.
 */

/* ── Tokens ───────────────────────────────────────────────────────────── */

:root {
  color-scheme: light dark;

  --ground: #f4f3f1;
  --card: #ffffff;
  --sunken: #eae8e4;
  --hairline: #dcd9d4;
  --border: #85817a;    /* 3.49:1 on ground, 3.17:1 on sunken - WCAG 1.4.11 */

  --ink: #22242a;        /* 13.99:1 on ground */
  --ink-soft: #4d515a;   /* 7.17:1 */
  --ink-mute: #636870;   /* 5.06:1 ground, 4.58:1 sunken, 5.61:1 card */

  /* Product text steps. The brand accents themselves are fills, rules and
     marks only: #9184d9 manages 2.99:1 on this ground and #d67f48 3.1:1,
     so neither may ever carry body text here. */
  --thread-text: #5d5294;      /* 6.11:1 */
  --emberside-text: #8c491a;   /* 6.14:1 */
  --sage-text: #56633f;        /* 5.82:1 */

  /* Brand fills, unchanged from each product's own files. */
  --thread-accent: #9184d9;
  --thread-night: #161826;
  --emberside-flame: #d67f48;  /* what every Emberside brand SVG paints */
  --emberside-sage: #728157;
  --emberside-cream: #f5ead8;
  --emberside-night: #1a1816;

  /* Foregrounds for surfaces that are dark in BOTH themes. Emberside's own
     CSS documents why these exist: a band using the theme's own light token
     painted #1a1816 on #1a1816 and vanished. Never redefine these in a dark
     block. */
  --on-night: #eceef1;
  --on-night-soft: #aeb2ba;

  /* Devices need a cast shadow to read as objects. Black at low alpha is
     invisible on a dark ground, so this is a token and the dark blocks
     deepen it rather than inheriting a shadow nobody can see. */
  --shade: 0 22px 45px -20px rgba(20, 22, 28, 0.38);

  /* The one raised-voice surface. It is deep in BOTH themes rather than
     fixed: on paper it is a dark band, and on a dark ground it is a well
     below it. Fixing it would have made it lighter than the dark ground,
     which is the opposite of what the word means. Its foregrounds are
     --on-night and --on-night-soft, which is what those were declared for. */
  --deep: #1f2127;

  --measure: 64ch;
  --radius: 14px;
  --gap: clamp(2.5rem, 6vw, 4.5rem);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ground: #1b1c20;
    --card: #24262b;
    --sunken: #2e3138;
    --hairline: #33363d;
    --border: #7c818a;  /* 4.35:1 on ground, 3.33:1 on sunken */

    --ink: #eceef1;       /* 14.65:1 on ground */
    --ink-soft: #aeb2ba;  /* 8.01:1 */
    --ink-mute: #949aa4;  /* 6.01:1 ground, 4.60:1 sunken, 5.35:1 card */

    --thread-text: #b5abfc;     /* 8.27:1 */
    --emberside-text: #f6a06b;  /* 8.24:1 */
    --sage-text: #aebf92;       /* 8.65:1 */
    --shade: 0 22px 48px -18px rgba(0, 0, 0, 0.62);
    --deep: #111216;
  }
}

:root[data-theme="dark"] {
  --ground: #1b1c20;
  --card: #24262b;
  --sunken: #2e3138;
  --hairline: #33363d;
  --border: #7c818a;
  --ink: #eceef1;
  --ink-soft: #aeb2ba;
  --ink-mute: #949aa4;
  --thread-text: #b5abfc;
  --emberside-text: #f6a06b;
  --sage-text: #aebf92;
  --shade: 0 22px 48px -18px rgba(0, 0, 0, 0.62);
  --deep: #111216;
}

/* ── Reset and base ───────────────────────────────────────────────────── */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 17px;
  line-height: 1.6;
  font-feature-settings: "cv05" 1;
  -webkit-font-smoothing: antialiased;
}

img, svg { max-width: 100%; height: auto; display: block; }

a { color: var(--ink); text-underline-offset: 3px; text-decoration-thickness: 1px; }
a:hover { text-decoration-color: var(--ink-mute); }

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

/* ── The joint site's own voice ─────────────────────────────────────────
 *
 * The site loaded three faces and spent them all on the products: Caprasimo
 * on Emberside's page, Figtree in its eyebrows, Inter everywhere else. So
 * the joint identity was the plainest thing on its own site, which is most
 * of why it read as clean rather than as anything.
 *
 * Fraunces is the joint voice. It is a soft old-style serif with a wonk axis
 * turned on, which gives it warmth without whimsy, and it is nothing like
 * Caprasimo's fat rounded display, so the two never look like they are
 * competing for the same job. The rule underneath is: THE JOINT SITE SPEAKS
 * IN FRAUNCES, AND EACH PRODUCT PAGE SPEAKS IN ITS OWN. Thread's site is
 * Inter throughout and its page here stays Inter; Emberside's is Caprasimo
 * and its page here stays Caprasimo.
 */

h1, h2, h3 { line-height: 1.13; text-wrap: balance; margin: 0 0 0.5em; font-weight: 600; letter-spacing: -0.015em; }
h1, h2 {
  font-family: Fraunces, Georgia, "Times New Roman", serif;
  font-variation-settings: 'SOFT' 40, 'WONK' 1, 'opsz' 110;
  font-weight: 600;
}
h1 { font-size: clamp(2.3rem, 5.3vw, 3.65rem); line-height: 1.06; letter-spacing: -0.022em; }
h2 { font-size: clamp(1.75rem, 3.9vw, 2.6rem); letter-spacing: -0.018em; }
h3 { line-height: 1.25; }
h3 { font-size: 1.14rem; }
p { margin: 0 0 1.1em; max-width: var(--measure); }
p:last-child { margin-bottom: 0; }

.lede { font-size: clamp(1.1rem, 2.1vw, 1.3rem); color: var(--ink-soft); max-width: 54ch; }
.muted { color: var(--ink-mute); }
.small { font-size: 0.9rem; }

.eyebrow {
  font-size: 0.775rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin: 0 0 0.85em;
}

/* ── Layout ───────────────────────────────────────────────────────────── */

.wrap { width: min(100% - 2.5rem, 68rem); margin-inline: auto; }
.narrow { max-width: 44rem; }

section { padding-block: var(--gap); }
.band { background: var(--sunken); }

/* The one section per page that is allowed to raise its voice. Fixed in
   both themes, which is finally what --on-night and --on-night-soft were
   defined for: they were declared with a comment warning nobody to redefine
   them and then used by nothing. */
.band-deep {
  background: var(--deep);
  color: var(--on-night);
  /* In dark mode the well sits only 1.1:1 from the page ground, which is
     right for a surface and too little for an edge, so the boundary is
     drawn rather than left to the fill. */
  border-block: 1px solid rgba(236, 238, 241, 0.11);
}
.band-deep h1, .band-deep h2, .band-deep h3 { color: var(--on-night); }
.band-deep p, .band-deep .lede { color: var(--on-night-soft); }
/* A deep band is dark in BOTH themes, so anything on it that takes a theme
   token renders dark-on-dark in light mode. `.plain b` is `--ink`, which
   measured 1.04:1 here: not low contrast, invisible. `.plain li` is
   `--ink-soft` at 2.02:1. Both were found on the thanks page, and the same
   omission had already been fixed once for `.hero`; this is that fix mirrored,
   so the next list dropped into a deep band does not repeat it. */
.band-deep .plain li, .band-deep .small, .band-deep .facts .l { color: var(--on-night-soft); }
.band-deep .plain b, .band-deep .plain strong, .band-deep .facts .n { color: var(--on-night); }
.band-deep .plain li::before { background: #9096a2; }
.band-deep .eyebrow { color: #9096a2; }        /* 5.42:1 */
.band-deep a { color: var(--on-night); }
.band-deep .btn { background: var(--on-night); border-color: var(--on-night); color: var(--deep); }
.band-deep .btn-quiet { background: transparent; color: var(--on-night); border-color: rgba(236, 238, 241, 0.42); }
.band-deep .btn-quiet:hover { background: rgba(236, 238, 241, 0.12); }
.band-deep .cta-note { color: #9096a2; }
.band-deep :focus-visible { outline-color: var(--on-night); }
.band-deep .card { background: rgba(236, 238, 241, 0.05); border-color: rgba(236, 238, 241, 0.14); }
.band-deep .card p { color: var(--on-night-soft); }

/* Two sections on the same ground read as one stretch, so they share one gap
   rather than stacking two. A band, or the section after a band or a product
   hero, starts its own stretch and keeps its full top padding. */
main > section + section { padding-top: 0; }
main > .band,
main > .band-deep,
main > .band + section,
main > .band-deep + section,
main > .hero + section { padding-top: var(--gap); }
.band-deep { padding-block: calc(var(--gap) * 1.25); }

hr.rule { border: 0; border-top: 1px solid var(--hairline); margin: 0; }

/* ── Header ───────────────────────────────────────────────────────────── */

.site-head {
  border-bottom: 1px solid var(--hairline);
  position: sticky;
  top: 0;
  background: color-mix(in srgb, var(--ground) 92%, transparent);
  backdrop-filter: saturate(1.4) blur(8px);
  z-index: 10;
}
.site-head .wrap {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  flex-wrap: wrap;
  padding-block: 0.85rem;
}
.brand {
  font-family: Fraunces, Georgia, serif;
  font-variation-settings: 'SOFT' 40, 'WONK' 1, 'opsz' 20;
  font-weight: 600;
  font-size: 1.12rem;
  text-decoration: none;
  letter-spacing: -0.01em;
  margin-right: auto;
}
.brand span { color: var(--ink-mute); font-weight: 400; }

.site-nav { display: flex; gap: 1.35rem; flex-wrap: wrap; font-size: 0.94rem; }
.site-nav a { color: var(--ink-soft); text-decoration: none; padding-block: 0.2rem; }
.site-nav a:hover { color: var(--ink); text-decoration: underline; }
.site-nav a[aria-current="page"] { color: var(--ink); font-weight: 500; }

/* On a phone the brand takes its own row and the nav wraps under it. Left at
   the desktop gap that costs a third of the first screen before any content,
   so both gaps tighten and the nav steps down a size. */
@media (max-width: 44rem) {
  .site-head .wrap { gap: 0.55rem 1.1rem; padding-block: 0.7rem; }
  .site-nav { gap: 0.4rem 1.05rem; font-size: 0.9rem; }
}

/* ── Buttons ──────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 0 1.3rem;
  border-radius: 10px;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--ground);
  font: inherit;
  font-weight: 500;
  text-decoration: none;
  transition: opacity 120ms ease;
}
.btn:hover { opacity: 0.86; }
.btn-quiet { background: transparent; color: var(--ink); border-color: var(--border); }
.btn-quiet:hover { background: var(--sunken); opacity: 1; }

.cta-row { display: flex; flex-wrap: wrap; gap: 0.85rem; align-items: center; margin-top: 1.9rem; }
.cta-note { margin-top: 0.9rem; font-size: 0.9rem; color: var(--ink-mute); }

/* ── Cards and grids ──────────────────────────────────────────────────── */

.grid, .grid-2, .grid-4 { margin-top: 0; }
.narrow + .grid, .narrow + .grid-2, .narrow + .grid-4 { margin-top: 2rem; }
.stack-top { margin-top: 1.6rem; }

/* Every floor is min(Nrem, 100%). A bare `minmax(24rem, 1fr)` cannot shrink
   below 384px, and .wrap at a 375px viewport is 335px, so the track overhung
   the page by 29px and every page carrying a four-card grid scrolled
   sideways. The min() lets the track collapse to the container instead. */
.grid { display: grid; gap: 1.1rem; grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr)); }
.grid-2 { display: grid; gap: 1.4rem; grid-template-columns: repeat(auto-fit, minmax(min(21rem, 100%), 1fr)); }
.grid-4 { display: grid; gap: 1.1rem; grid-template-columns: repeat(auto-fit, minmax(min(24rem, 100%), 1fr)); }

.card {
  background: var(--card);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
}
/* A card heading is a ROLE, not a level. Some are h2 (the pricing facts sit
   directly under that page's h1 and have no parent section) and some are h3,
   and both must look the same and stay out of the display face. Scoped through
   the grid so the specificity is 0,2,1 and beats the per-page voice rules on
   their own merits rather than on source order, which is the failure the audit
   caught once already. */
.grid .card h2, .grid .card h3,
.grid-2 .card h2, .grid-2 .card h3,
.grid-4 .card h2, .grid-4 .card h3 {
  font-family: Inter, ui-sans-serif, system-ui, sans-serif;
  font-variation-settings: normal;
  font-size: 1.14rem;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.012em;
  margin-bottom: 0.4em;
}
.card h3 { margin-bottom: 0.4em; }
.card p { color: var(--ink-soft); font-size: 0.97rem; }

/* A screenshot always sits on a card with a hairline, never on the bare
   ground. Thread's shot is a large field of #161826 and Emberside's is a
   large field of cream, so on a naive neutral each one blends into the wrong
   theme's ground and stops reading as an object. */
.shot {
  background: var(--card);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 0.6rem;
  overflow: hidden;
}
.shot img { border-radius: 8px; }

/* ── The screenshot carousel ──────────────────────────────────────────────
 *
 * A scroll container, not a widget. The site's CSP is script-src 'none' and
 * that is a decision rather than an accident, so the options were a radio-and-
 * :checked contraption pretending to be a carousel, or the thing the browser
 * already does well. This is the second one: native momentum and swipe on a
 * phone, a real scrollbar on a desktop, and it degrades to a plain row of
 * pictures if every line of this block fails to load.
 *
 * tabindex="0" in the markup is load-bearing rather than decorative. A scroll
 * container that keyboard focus cannot reach is content a keyboard user cannot
 * see, so the container takes focus and the arrow keys scroll it.
 *
 * The slide is sized to leave a sliver of the next one showing. That sliver is
 * the only affordance here saying there is more, and losing it to a round
 * number would make the carousel look like a single cropped screenshot.
 */

.carousel {
  display: flex;
  gap: 0.85rem;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  padding-bottom: 0.85rem;
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}
.carousel:focus-visible { outline: 2px solid var(--ink); outline-offset: 6px; border-radius: 4px; }

.slide {
  flex: 0 0 auto;
  width: min(80%, 290px);
  margin: 0;
  scroll-snap-align: center;
}
.slide img {
  width: 100%;
  height: auto;
  border-radius: 10px;
  background: var(--card);
  border: 1px solid var(--hairline);
  padding: 0.4rem;
}
.slide figcaption {
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-mute);
  padding-top: 0.7rem;
  text-align: center;
}

/* Each slide opens. The trigger is a real <button> wrapping the image, so it
   is reachable by keyboard and announces itself; the zoom-in cursor is the
   only thing telling a mouse user, so it is not decoration. */
.slide-open {
  display: block;
  /* Positioned so the visually-hidden label inside it has a containing block.
     Without this the .vh spans resolve against the page, which means the
     carousel's overflow cannot clip them: they sat at their static positions
     out to x=1280 and stretched the document to 1280px wide on a 375px phone.
     Measured, not guessed — the five spans landed at 154, 436, 717, 999 and
     1280, and 1280 was exactly documentElement.scrollWidth. */
  position: relative;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
  font: inherit;
  color: inherit;
}
.slide-open:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; border-radius: 12px; }
.hero .slide-open:focus-visible { outline-color: var(--hero-ink); }
.slide-open:hover img { border-color: var(--border); }

/* Screen-reader-only. The button's name comes from the image's alt, and this
   adds what the picture cannot say: that it does something when you press it.

   Anything using this class must be inside a positioned ancestor. An absolutely
   positioned element with none takes the page as its containing block, escapes
   every overflow between it and the root, and then sizes the document from its
   static position — which is how one hidden word made this page scroll
   sideways. */
.vh {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ── The enlarged view ────────────────────────────────────────────────────
 *
 * The HTML popover attribute, which is markup rather than script — so it
 * works under script-src 'none', and it is the only reason this feature can
 * exist on this site at all. What it brings for free is the part that is
 * tedious and easy to get wrong by hand: Escape closes it, clicking outside
 * closes it, it renders in the top layer so no z-index can fight it, focus
 * moves into it and returns to the button afterwards, and ::backdrop is a
 * real element to style.
 *
 * An auto popover moves focus only if something inside asks for it, so the
 * close button carries autofocus. Without it the keyboard stays on <body>
 * behind an open lightbox, which is the state where Escape is the only way
 * out and nothing has said so.
 *
 * `display: none` on the closed state is the safety net. In a browser too old
 * to know the attribute, `popover` is ignored and these would otherwise be
 * five full-size screenshots dumped into the page; hidden, the button simply
 * does nothing and the carousel is unharmed.
 */
.lightbox {
  display: none;
  position: fixed;
  inset: 0;
  /* inset:0 is the correct way to fill the viewport and `width: auto` is what
     cancels the UA's `width: fit-content` for [popover]. The two max-* lines
     below are a clamp rather than a size: where the fixed-position containing
     block is the viewport, as it is in a real browser, inset:0 already gives
     exactly these dimensions and the clamps do nothing at all.

     They are here because a containing block can be wrong and this fails
     loudly when it is. Measured in device emulation: 100vw reports 375 and
     document.clientWidth reports 375, while window.innerWidth reports 1280 and
     a bare `position:fixed; inset:0` element comes out 1280 x 2772 — so the
     panel rendered 1280px wide on a 375px screen, took the page sideways with
     it, and centred its image off-screen, which then stopped the lazy image
     ever loading because it was never near the viewport. One wrong number,
     three symptoms. vw and dvh stayed correct throughout, so they are what
     this leans on; dvh rather than vh because mobile browser chrome moves. */
  width: auto;
  height: auto;
  max-width: 100vw;
  max-height: 100vh;
  max-height: 100dvh;
  margin: 0;
  padding: clamp(1rem, 4vw, 2.5rem);
  border: 0;
  background: transparent;
  overflow: auto;
}
.lightbox:popover-open {
  display: grid;
  grid-template-rows: 1fr auto;
  justify-items: center;
  align-content: center;
  gap: 1rem;
}
.lightbox::backdrop { background: rgba(16, 17, 21, 0.82); }
.lightbox img {
  max-width: 100%;
  max-height: 78vh;
  width: auto;
  height: auto;
  border-radius: 12px;
  box-shadow: 0 30px 70px -25px rgba(0, 0, 0, 0.7);
}
.lightbox-close {
  font: inherit;
  font-size: 0.9rem;
  font-weight: 500;
  min-height: 44px;
  padding: 0 1.4rem;
  border-radius: 10px;
  border: 1px solid rgba(236, 238, 241, 0.4);
  background: rgba(236, 238, 241, 0.1);
  color: #eceef1;
  cursor: pointer;
}
.lightbox-close:hover { background: rgba(236, 238, 241, 0.2); }
.lightbox-close:focus-visible { outline: 2px solid #eceef1; outline-offset: 3px; }

/* On a product hero the slides sit on that product's fixed ground, so they
   take the hero's surface tokens rather than the theme's. */
.hero .slide img { background: var(--hero-shot); border-color: var(--hero-edge-strong); }
.hero .slide figcaption { color: var(--hero-mute); }

/* The scrollbar is the desktop affordance and the sliver is the touch one;
   a hidden scrollbar would leave a mouse user with only the sliver. */
.carousel::-webkit-scrollbar { height: 8px; }
.carousel::-webkit-scrollbar-track { background: transparent; }
.carousel::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
.hero .carousel { scrollbar-color: var(--hero-edge-strong) transparent; }
.hero .carousel::-webkit-scrollbar-thumb { background: var(--hero-edge-strong); }

@media (prefers-reduced-motion: reduce) {
  .carousel { scroll-behavior: auto; }
}

.split { display: grid; gap: var(--gap); align-items: center; grid-template-columns: 1fr; }
@media (min-width: 62rem) { .split { grid-template-columns: 1.15fr 0.85fr; } }

/* The home hero carries two devices rather than one screenshot, so its image
   column needs room the product pages' single shot does not. */
@media (min-width: 62rem) { .split.split-devices { grid-template-columns: 1.06fr 0.94fr; } }

/* ── Two devices, one plan ────────────────────────────────────────────────
 *
 * The hero showed Thread alone, which under-sold a site that only sells the
 * pair. Both frames are drawn here rather than baked into a raster mockup: a
 * mockup carries its own background, and a fixed background is exactly the
 * thing that breaks on a page rendering in two themes.
 *
 * The colour in this composition is the products' own — Thread's night ground
 * against Emberside's cream — so the hero gets its contrast from the two apps
 * rather than from the joint site adopting a hue it has argued it should not
 * have.
 */

.devices {
  display: flex;
  align-items: center;
  justify-content: center;
  padding-block: 1.2rem;
}

.device {
  margin: 0;
  border-radius: 24px;
  padding: 7px;
  background: var(--card);
  border: 1px solid var(--hairline);
  box-shadow: var(--shade);
}
.device img { border-radius: 17px; width: 100%; height: auto; }
.device figcaption {
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-mute);
  text-align: center;
  padding-top: 0.6rem;
}

/* The tablet sits behind and tilts away; the phone comes forward over it.
   Thread is the app a member opens every day, so it is the one in front. */
.device-tablet { width: min(57%, 310px); transform: rotate(-4.5deg); z-index: 1; }
.device-phone  { width: min(39%, 205px); margin-left: -13%; transform: rotate(4deg); z-index: 2; }

/* Under 62rem the hero is one column and the pair has the full width, so they
   can grow. Below 30rem the overlap starts stealing legibility from both, so
   the angles flatten and the phone steps out from behind. */
@media (max-width: 61.99rem) {
  .device-tablet { width: min(52%, 340px); }
  .device-phone  { width: min(35%, 224px); }
}
@media (max-width: 30rem) {
  .device-tablet { transform: rotate(-2.5deg); }
  .device-phone  { transform: rotate(2.5deg); margin-left: -9%; }
}

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

/* ── Per-page product voice ───────────────────────────────────────────────
 *
 * Each product page carries its product's own type and its own text colour
 * below the hero, on the joint site's neutral ground. Emberside's headings
 * are Caprasimo and its labels are Figtree, exactly as on embersideapp.com;
 * Thread sets everything in the body face, as it does on its own site.
 */

.display { font-family: Caprasimo, Georgia, serif; font-weight: 400; letter-spacing: -0.01em; }

.page-thread .eyebrow { color: var(--thread-text); }
.page-thread h1, .page-thread h2 {
  font-family: Inter, ui-sans-serif, system-ui, sans-serif;
  font-variation-settings: normal;
  letter-spacing: -0.025em;
}
.page-thread .card h3 { color: var(--ink); }

.page-emberside .eyebrow { font-family: Figtree, Inter, sans-serif; letter-spacing: 0.08em; color: var(--emberside-text); }
.page-emberside h1, .page-emberside h2 { font-family: Caprasimo, Georgia, serif; font-weight: 400; letter-spacing: -0.01em; }

/* ── Product hero bands ───────────────────────────────────────────────────
 *
 * A product page opens in that product's own world, using that product's own
 * hex values from its own stylesheet — Thread's #161826/#e9e9ed/#9184d9,
 * Emberside's #f5ead8/#201e1d/#c67139. These bands are a fixed surface in
 * BOTH themes, like --on-night, so nothing here is redefined in a dark
 * block: a hero that flipped with the theme would stop being the product's
 * ground and start being ours.
 *
 * Everything below the hero returns to the neutral chrome. The product's
 * colour marks where you have arrived; it does not run the whole page.
 */

.hero {
  background: var(--hero-ground);
  color: var(--hero-ink);
  border-bottom: 1px solid var(--hero-edge);
}
.hero h1, .hero h2, .hero h3 { color: var(--hero-ink); }
.hero p { color: var(--hero-soft); }
.hero .eyebrow { color: var(--hero-accent); }
.hero .cta-note { color: var(--hero-mute); }
.hero .btn { background: var(--hero-cta); border-color: var(--hero-cta); color: var(--hero-cta-ink); }
.hero .btn-quiet {
  background: transparent;
  border-color: var(--hero-edge-strong);
  color: var(--hero-ink);
}
.hero .btn-quiet:hover { background: var(--hero-edge); }
.hero .shot { background: var(--hero-shot); border-color: var(--hero-edge-strong); }
.hero a { color: var(--hero-ink); }
.hero .plain li, .hero .small, .hero .facts .l { color: var(--hero-soft); }
.hero :focus-visible { outline-color: var(--hero-ink); }

.hero-thread {
  --hero-ground: #161826;
  --hero-ink: #e9e9ed;      /* 14.54:1 */
  --hero-soft: #cfd3e5;     /* 11.82:1 */
  --hero-mute: #9397ab;     /*  6.08:1 */
  --hero-accent: #b5abfc;   /*  8.55:1 */
  --hero-cta: #e9e9ed;
  --hero-cta-ink: #161826;
  --hero-shot: #232532;
  --hero-edge: rgba(233, 233, 237, 0.14);
  --hero-edge-strong: rgba(233, 233, 237, 0.45);
}

.hero-emberside {
  --hero-ground: #f5ead8;
  --hero-ink: #201e1d;      /* 13.95:1 */
  --hero-soft: #544c42;     /*  7.09:1 */
  --hero-mute: #675e51;     /*  5.35:1 */
  --hero-accent: #8c491a;   /*  5.72:1 */
  --hero-cta: #b35f2c;
  --hero-cta-ink: #ffffff;  /*  4.58:1 on the fill */
  --hero-shot: #faf3e6;
  --hero-edge: rgba(32, 30, 29, 0.10);
  --hero-edge-strong: rgba(32, 30, 29, 0.38);
}
.hero-emberside .eyebrow { font-family: Figtree, Inter, sans-serif; letter-spacing: 0.08em; }
.lockup { width: clamp(15rem, 26vw, 19rem); height: auto; margin-bottom: 1.5rem; }
.lockup-row { display: flex; align-items: center; gap: 0.85rem; margin-bottom: 1.5rem; }
.lockup-row img { width: 52px; height: 52px; border-radius: 12px; flex: 0 0 auto; }
.lockup-row b { display: block; font-size: 1.5rem; font-weight: 600; letter-spacing: -0.025em; line-height: 1.1; }
.lockup-row .sub {
  display: block;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.19em;
  text-transform: uppercase;
  color: var(--hero-mute);
  margin-top: 0.25rem;
}
.hero-emberside .display { font-family: Caprasimo, Georgia, serif; font-weight: 400; letter-spacing: -0.01em; }

/* ── Product blocks: the only place colour appears ────────────────────── */

.product {
  border-radius: var(--radius);
  padding: 1.9rem;
  border: 1px solid var(--hairline);
  background: var(--card);
  display: flex;
  flex-direction: column;
}
.product .more-row, .card .more-row { margin-top: auto; padding-top: 1.4rem; }
.product .eyebrow { margin-bottom: 0.6em; }
.product h2, .product h3 { margin-top: 0; }
.product .mark { display: flex; align-items: center; gap: 0.7rem; margin-bottom: 1.1rem; }
.product .mark img, .product .mark svg { width: 38px; height: 38px; border-radius: 9px; flex: 0 0 auto; }
.product .mark b { font-size: 1.15rem; letter-spacing: -0.02em; }

.product-thread { border-top: 3px solid var(--thread-accent); }
.product-thread .eyebrow,
.product-thread .more { color: var(--thread-text); }

.product-emberside { border-top: 3px solid var(--emberside-flame); }
.product-emberside .eyebrow { font-family: Figtree, Inter, sans-serif; letter-spacing: 0.08em; }
.product-emberside .eyebrow,
.product-emberside .more { color: var(--emberside-text); }
.product-emberside .display { font-family: Caprasimo, Georgia, serif; font-weight: 400; letter-spacing: -0.01em; }

.more { font-weight: 500; text-decoration: none; }
.more:hover { text-decoration: underline; }

/* ── Lists ────────────────────────────────────────────────────────────── */

.plain { list-style: none; padding: 0; margin: 0 0 1.1em; max-width: var(--measure); }
.plain li { padding-left: 1.35rem; position: relative; margin-bottom: 0.6em; color: var(--ink-soft); }
.plain li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.62em;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--border);
}
.plain b, .plain strong { color: var(--ink); font-weight: 500; }

.facts { display: flex; flex-wrap: wrap; gap: 2.2rem; margin: 1.8rem 0 0; padding: 0; list-style: none; }
.facts li { display: flex; flex-direction: column; }
.facts .n { font-size: 1.9rem; font-weight: 600; letter-spacing: -0.03em; line-height: 1.1; }
.facts .l { font-size: 0.83rem; color: var(--ink-mute); letter-spacing: 0.04em; text-transform: uppercase; }

/* ── Video slot ───────────────────────────────────────────────────────── */

.video {
  max-width: 880px;
  margin-inline: auto;
  aspect-ratio: 16 / 9;
  background: var(--card);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  display: grid;
  place-items: center;
  text-align: center;
  padding: 1.5rem;
  color: var(--ink-mute);
  font-size: 0.94rem;
}
/* Once a real video is in the slot, the placeholder dressing has to come off:
   the dashed border and the 1.5rem of padding were drawn to say "something
   goes here", and around an actual player they read as a picture frame. The
   ground goes dark because that is what letterboxes to, and because it is what
   the player fades up from while it loads. */
.video.is-live {
  padding: 0;
  border: 0;
  background: var(--deep);
  overflow: hidden;
}
.video iframe { display: block; width: 100%; height: 100%; border: 0; }

/* ── Footer ───────────────────────────────────────────────────────────── */

.site-foot {
  border-top: 1px solid var(--hairline);
  padding-block: 2.6rem 3.4rem;
  font-size: 0.92rem;
  color: var(--ink-mute);
}
.site-foot nav { display: flex; flex-wrap: wrap; gap: 1.2rem; margin: 0.9rem 0; }
.site-foot a { color: var(--ink-soft); text-decoration: none; }
.site-foot a:hover { text-decoration: underline; color: var(--ink); }
.site-foot .parent { color: var(--ink); text-decoration: none; font-weight: 500; }

/* ── Motion ───────────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}
