@import url('https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,600;0,700;1,400;1,700&family=UnifrakturMaguntia&family=Cardo:ital,wght@0,400;0,700;1,400&display=swap');

/* GENERATED by build_site.py from theme/ — DO NOT EDIT HERE.
   Edit theme/base.css, theme/parts/*.css or
   theme/universes/*.css in the author-websites repo and rebuild.
   Brand: leslienairn (leslienairn.com) · layout: singleworld
   Universes: knowledge (default) */

:root {  /* default universe: knowledge */
  --sand:       #e9e6df;
  --paper:      #faf9f6;
  --white:      #ffffff;
  --ink:        #16151a;
  --sea:        #4a4453;
  --sea-fg:     #faf9f6;
  --rope:       #8d8172;
  --accent:     #7d2626;
  --accent-fg:  #faf9f6;
  --muted:      #6d6a70;
  --body:       #57545a;
  --rule:       rgba(0,0,0,0.15);
  --nav-bg:     #16151a;
  --nav-fg:     #e9e6df;
  --nav-dim:    #a6a1a8;
  --nav-cta:    #b98a8a;
  --nav-track:  .03em;
  --wordmark:      'UnifrakturMaguntia', 'Cardo', serif;
  --display:       'Cardo', 'EB Garamond', Garamond, serif;
  --label:         'Cardo', Garamond, serif;
  --wordmark-size: 1.5rem;
  --wordmark-track: .02em;
  --display-track: .01em;
  --label-track:   .04em;
  --label-track-sm: .04em;
  --wallpaper:      url('/images_cache/wallpaper_knowledge_warm.jpg');
  --wallpaper-size: 600px 600px;
  --cover-fallback: #100e13;
  --placeholder-bg: linear-gradient(160deg, #d9d5cc, #bdb7ad);
}
body.uni-knowledge {  /* knowledge */
  --sand:       #e9e6df;
  --paper:      #faf9f6;
  --white:      #ffffff;
  --ink:        #16151a;
  --sea:        #4a4453;
  --sea-fg:     #faf9f6;
  --rope:       #8d8172;
  --accent:     #7d2626;
  --accent-fg:  #faf9f6;
  --muted:      #6d6a70;
  --body:       #57545a;
  --rule:       rgba(0,0,0,0.15);
  --nav-bg:     #16151a;
  --nav-fg:     #e9e6df;
  --nav-dim:    #a6a1a8;
  --nav-cta:    #b98a8a;
  --nav-track:  .03em;
  --wordmark:      'UnifrakturMaguntia', 'Cardo', serif;
  --display:       'Cardo', 'EB Garamond', Garamond, serif;
  --label:         'Cardo', Garamond, serif;
  --wordmark-size: 1.5rem;
  --wordmark-track: .02em;
  --display-track: .01em;
  --label-track:   .04em;
  --label-track-sm: .04em;
  --wallpaper:      url('/images_cache/wallpaper_knowledge_warm.jpg');
  --wallpaper-size: 600px 600px;
  --cover-fallback: #100e13;
  --placeholder-bg: linear-gradient(160deg, #d9d5cc, #bdb7ad);
}
:root {  /* brand structure */
  --content-max: 920px;
  --nav-max: 980px;
  --series-card-gap: 1.25rem;
  --series-card-img-h-sm: 220px;
  --shadow-hover: 0 4px 16px rgba(0,0,0,0.14);
}

/* ═══════════════════════════════════════════════════════════════════════════
   base.css — the shared system behind all four author-brand sites (R2900).

   NOTHING HERE NAMES A COLOUR OR A FACE. Every one comes from a token, and the
   tokens come from theme/universes/<universe>.css: the palette AND the display
   face are per-UNIVERSE, not per-brand, because thebondi and thegoldcoast share
   one byline, one site and one nav bar and take different faces (ruled
   2026-08-30). Colour already themed per universe via body.uni-*; type joins it.

   Compiled — never served. build_site.py concatenates:
       @import <fonts>  ·  :root{default universe}  ·  body.uni-*{each universe}
       ·  this file  ·  the brand's theme/parts/*.css
   into <site>/css/style.css, which the site repo commits. A site stays
   standalone-servable; this repo is only needed to rebuild it.

   Structural values that differ between the two live sites for no reason
   anyone recorded (main 900 vs 920, nav 960 vs 980) are brand tokens holding
   each site's own value, not a unification nobody asked for.
   ═══════════════════════════════════════════════════════════════════════════ */

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }

/* Body face is Lora on all five universes and is deliberately NOT a token. The
   house voice is the one thing that must not vary, or five sites by one author
   stop reading as one hand. */
body {
  font-family: 'Lora', Georgia, 'Times New Roman', serif;
  color: var(--ink);
  background-color: var(--sand);
  background-image: var(--wallpaper);
  background-size: var(--wallpaper-size);
  line-height: 1.6;
  font-size: 1rem;
}

/* --wordmark carries the nav brand and the page H1; --display carries the
   heading grid. Separate because a script (Yellowtail) or a blackletter
   (Textura) can carry a wordmark and cannot carry a heading grid. */
h1, .nav-brand { font-family: var(--wordmark); letter-spacing: var(--wordmark-track); }
h2, h3 { font-family: var(--display); letter-spacing: var(--display-track); }

/* ── Navigation ─────────────────────────────────────────── */
/* Was `background: var(--ink)` — the nav colour WAS the body-text colour, so a
   dark ground inverted the nav with it. --nav-bg/--nav-fg break that knot. */
nav { background: var(--nav-bg); padding: 0 1.5rem; }
.nav-inner {
  max-width: var(--nav-max); margin: 0 auto;
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 0.5rem; padding: 0.75rem 0;
}
.nav-brand {
  color: var(--nav-fg); text-decoration: none; font-size: var(--wordmark-size);
  letter-spacing: var(--wordmark-track); margin-right: auto; white-space: nowrap;
}
nav ul {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 0.25rem 1.5rem;
}
nav ul a {
  color: var(--nav-dim); text-decoration: none; font-size: 0.9rem;
  letter-spacing: var(--nav-track); transition: color 0.15s;
}
nav ul a:hover, nav ul a.active { color: var(--nav-fg); }
nav ul a.cta-link { color: var(--nav-cta); }
nav ul a.cta-link:hover { color: var(--nav-fg); }

/* ── Page header ─────────────────────────────────────────── */
.page-header {
  text-align: center; padding: 3.5rem 1.5rem 2.5rem;
  border-bottom: 1px solid var(--rule);
}
.page-header h1 { font-size: clamp(1.8rem, 4vw, 2.8rem); margin: 0 0 0.5rem; color: var(--ink); }
.page-header .subtitle { color: var(--sea); font-style: italic; margin: 0; font-size: 1.1rem; }

/* ── Buttons ─────────────────────────────────────────────── */
/* Hover is filter: brightness(), never a second hardcoded colour. The old Bondi
   pair (#a83f1f / #245a73) was a hand-picked darkening of ONE palette and is
   silently wrong under any other — the same defect class as the hardcoded #444.
   --accent-fg exists because an amber accent needs DARK text on it: a contrast
   fix, not a preference. */
.btn {
  display: inline-block; text-decoration: none; padding: 0.85rem 2rem;
  border-radius: 4px; font-weight: bold; font-size: 1.05rem; transition: filter 0.15s;
  font-family: var(--label); letter-spacing: var(--label-track);
}
.btn-primary { background: var(--accent); color: var(--accent-fg); }
.btn-primary:hover { filter: brightness(0.9); }
.btn-secondary { background: var(--sea); color: var(--sea-fg); }
.btn-secondary:hover { filter: brightness(0.9); }

/* ── Main content wrapper ────────────────────────────────── */
main { max-width: var(--content-max); margin: 0 auto; padding: 3rem 1.5rem 5rem; }
main.narrow { max-width: 720px; }

/* ── Section spacing ─────────────────────────────────────── */
section { margin-bottom: 3.5rem; }
section h2 {
  font-size: 1.5rem; color: var(--sea); margin: 0 0 0.75rem;
  padding-bottom: 0.4rem; border-bottom: 1px solid var(--rule);
}

/* ── Series card (hub + worlds) ──────────────────────────── */
.series-card {
  background: var(--paper); border: 1px solid var(--rule); border-radius: 6px;
  overflow: hidden; display: flex; gap: 1.5rem; padding: 1.5rem;
  text-decoration: none; color: inherit; transition: box-shadow 0.15s;
  margin-bottom: var(--series-card-gap);
}
.series-card:hover { box-shadow: var(--shadow-hover); }
.series-card img { width: 120px; flex-shrink: 0; object-fit: cover; border-radius: 3px; }
.series-card-body h3 { margin: 0 0 0.3rem; font-size: 1.3rem; color: var(--ink); }
.series-card-body .series-sub {
  font-family: var(--label); color: var(--rope); font-style: italic;
  margin: 0 0 0.75rem; font-size: 0.95rem;
}
.series-card-body p { margin: 0 0 1rem; color: var(--body); font-size: 0.95rem; }

/* ⚠ UNSCOPED on purpose. This was only ever defined as `.series-card-body
   .card-link` and `.world-card .card-link`, so the class did nothing outside a
   card - and a hand-authored page using it got the BROWSER default, which turns
   purple once visited. Caught on the two new brands' home pages, where the
   "Enter The Knowledge/Black" links rendered as underlined purple against the
   palette. A class named .card-link should mean the same thing wherever it is
   written; the two scoped rules below are now redundant but harmless. */
.card-link {
  color: var(--sea); font-weight: bold; font-size: 0.95rem; text-decoration: none;
}
.card-link:hover { text-decoration: underline; }
.series-card-body .card-link { color: var(--sea); font-weight: bold; font-size: 0.95rem; }

/* ── Books grid (series page) ────────────────────────────── */
.books-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 2rem; }
.book-card { background: var(--paper); border: 1px solid var(--rule); border-radius: 6px; overflow: hidden; }
.book-card img {
  width: 100%; display: block; aspect-ratio: 2/3; object-fit: cover;
  background: var(--cover-fallback);
}
.book-card-body { padding: 1rem 1.25rem 1.25rem; }
.book-card-body .book-num {
  font-family: var(--label); color: var(--rope);
  font-size: 0.85rem; font-weight: bold; margin: 0 0 0.2rem;
}
.book-card-body h3 { font-size: 1.1rem; margin: 0 0 0.75rem; }
.book-card-body .blurb { font-size: 0.9rem; color: var(--body); line-height: 1.55; margin: 0 0 1rem; }
.book-card-body .status { font-size: 0.85rem; color: var(--muted); font-style: italic; }
.book-card-body .status.free { color: var(--sea); font-style: normal; font-weight: bold; }

/* A magnet is an ordinary book SOLD at a price; giving it to the list is a
   separate, occasional route, not a price (ruled 2026-08-29). These mark the
   route beside the ordinary buy row. */
.badge-free {
  display: inline-block; background: var(--sea); color: var(--sea-fg);
  font-family: var(--label); font-size: 0.7rem; font-weight: bold;
  letter-spacing: .06em; text-transform: uppercase;
  padding: 2px 7px; border-radius: 3px; vertical-align: middle; margin-right: 0.4em;
}
.book-card--free { border-color: var(--sea); }
.series-card--free { border-left: 3px solid var(--sea); }

/* ── Per-book buy/read links ─────────────────────────────── */
.book-links { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.75rem; }
.btn-small {
  display: inline-block; text-decoration: none; padding: 0.4rem 1rem;
  border-radius: 4px; font-size: 0.85rem; font-weight: bold;
  font-family: var(--label); letter-spacing: var(--label-track-sm);
  background: var(--accent); color: var(--accent-fg); transition: filter 0.15s;
}
.btn-small:hover { filter: brightness(0.9); }
.btn-small.alt { background: var(--sea); color: var(--sea-fg); }

/* ── CTA box ─────────────────────────────────────────────── */
.cta-box {
  background: var(--paper); border: 1px solid var(--rule); border-radius: 6px;
  padding: 2.5rem 2rem; text-align: center; margin: 3rem 0;
}
.cta-box h2 { border: none; color: var(--ink); font-size: 1.5rem; margin: 0 0 0.5rem; }
.cta-box p { color: var(--muted); margin: 0 0 1.5rem; }

/* ── World intro ─────────────────────────────────────────── */
.world-intro p { font-size: 1.05rem; max-width: 680px; }

/* ── Footer ──────────────────────────────────────────────── */
footer {
  text-align: center; padding: 2rem 1.5rem; color: var(--muted);
  font-size: 0.88rem; border-top: 1px solid var(--rule);
}
footer a { color: var(--sea); text-decoration: none; }
footer a:hover { text-decoration: underline; }

/* ── Responsive ──────────────────────────────────────────── */
@media (max-width: 600px) {
  .series-card { flex-direction: column; }
  .series-card img { width: 100%; height: var(--series-card-img-h-sm); }
  .books-grid { grid-template-columns: 1fr; }
  .nav-brand { font-size: 0.95rem; }
}

/* placeholder for books without cover art yet */
.cover-placeholder { background: var(--placeholder-bg); border-radius: 4px; min-height: 180px; width: 100%; }

/* parts/singleworld.css — components only the singleworld LAYOUT renders.
   One canonical /world/ page per brand (plus /world/<slug>/ for any additional
   universe the brand carries), an R1413 image manifest placing photo furniture,
   and the free-marketing-material download row.

   ⚠ The download row is the THIRD give-away channel and must not be collapsed
   into the other two: a book that is SOLD, a book given to the list, and
   marketing material free to everyone. Only this last one lives here. */

/* ── Download row (free marketing PDFs) ─────────────────── */
.download-row {
  display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem 1.25rem;
  background: var(--paper); border: 1px solid var(--rule); border-radius: 6px;
  padding: 1.25rem 1.5rem; margin: 1.5rem 0;
}
.download-row .dl-text { flex: 1; min-width: 220px; }
.download-row .dl-text strong { display: block; font-size: 1.05rem; }
.download-row .dl-text span { color: var(--muted); font-size: 0.9rem; }

/* ── Book card: POV character line ───────────────────────── */
.book-card-body .character {
  font-family: var(--label); font-size: 0.85rem; color: var(--rope);
  font-style: italic; margin: 0 0 0.85rem;
}

/* ── Illustration banner (world page) ────────────────────── */
.illustration-banner {
  width: 100%; border-radius: 6px;
  box-shadow: 0 3px 14px rgba(0,0,0,0.15);
  display: block; margin: 0 0 2.5rem;
}

/* ── Scattered photos (manifest-placed) ──────────────────── */
.photo-scatter {
  position: relative; height: 380px; max-width: 900px;
  margin: 0 auto 1rem; pointer-events: none;
}
.photo-scatter .snap {
  position: absolute; box-shadow: 3px 6px 22px rgba(0,0,0,0.22);
  max-height: 300px; width: auto;
}
.photo-scatter .snap:nth-child(1) { width: 270px; left: 2%;  top: 55px; transform: rotate(-5deg);  z-index: 1; }
.photo-scatter .snap:nth-child(2) { width: 420px; left: 22%; top: 15px; transform: rotate(2.5deg); z-index: 3; }
.photo-scatter .snap:nth-child(3) { width: 360px; right: 1%; top: 65px; transform: rotate(-2deg);  z-index: 2; }
/* Series page — 2-photo scatter */
.photo-scatter.two-up { height: 440px; }
.photo-scatter.two-up .snap:nth-child(1) { width: 520px; left: 3%;  top: 25px; transform: rotate(-3deg);  z-index: 1; }
.photo-scatter.two-up .snap:nth-child(2) { width: 480px; right: 2%; top: 55px; transform: rotate(3.5deg); z-index: 2; }

/* ── Featured standalone photo ───────────────────────────── */
.photo-feature {
  display: block; margin: 2.5rem auto; max-width: 700px; width: 100%;
  box-shadow: 4px 7px 24px rgba(0,0,0,0.2);
}
.photo-feature.tilt-left  { transform: rotate(-2deg); }
.photo-feature.tilt-right { transform: rotate(2deg); }
/* Portrait postcard — constrained narrower so it doesn't dominate */
.photo-feature.postcard { max-width: 430px; }
.photo-feature.postcard.tilt-left  { transform: rotate(-1.5deg); }
.photo-feature.postcard.tilt-right { transform: rotate(1.5deg); }

@media (max-width: 600px) {
  .photo-scatter {
    height: auto; display: flex; flex-direction: column;
    align-items: center; gap: 1.5rem; padding: 1rem 0 2rem;
  }
  .photo-scatter .snap,
  .photo-scatter.two-up .snap {
    position: static; width: 88%; max-width: 360px; max-height: none;
  }
  .photo-scatter .snap:nth-child(odd)  { transform: rotate(-2deg); }
  .photo-scatter .snap:nth-child(even) { transform: rotate(2deg); }
}

/* parts/home-leslienairn.css — hero + cinema for leslienairn.com's HAND-AUTHORED
   pages (home, about, newsletter, privacy). The generator never writes those
   pages; it does write the stylesheet they load, so their CSS lives here.

   ART DIRECTION, per BRAND — it names specific stills and a specific cross-fade
   rhythm. The four frames came across from johnnairn-website with the universe
   under R2900 P7; they were rendered for The Knowledge and belong to it.

   theknowledge sits on a LIGHT ground, so the scrim is a paper wash rather than
   johnnairn's dark one, and the caption takes --ink instead of a pale tone. The
   rhythm is the slowest of the four sites: 24s over four frames with barely any
   Ken Burns. A book about a printing press should not feel hurried. */

/* ── Hero (static fallback; the home page wraps it in .cinema-hero) ────────── */
.hero {
  text-align: center;
  padding: 5.5rem 1.5rem 4rem;
  border-bottom: 1px solid var(--rule);
  background:
    radial-gradient(90% 120% at 15% 0%, color-mix(in srgb, var(--accent) 14%, transparent) 0%, transparent 60%),
    linear-gradient(170deg, var(--paper) 0%, var(--sand) 70%);
}
.hero h1 { font-size: clamp(2.4rem, 6vw, 4rem); margin: 0 0 0.4rem; color: var(--ink); }
.hero .tagline { color: var(--sea); font-style: italic; font-size: 1.25rem; margin: 0 0 2rem; }
.hero .lead { max-width: 620px; margin: 0 auto 2.5rem; font-size: 1.12rem; color: var(--body); }

/* ── Cinema hero — cross-fading period stills. Pure CSS. ─────────────────── */
.cinema { position: relative; overflow: hidden; border-bottom: 1px solid var(--rule); }
.cinema-frames { position: absolute; inset: 0; z-index: 0; }
.cinema .frame {
  position: absolute; inset: 0;
  background-size: cover; background-position: center;
  opacity: 0; will-change: opacity, transform;
  animation: lnCinemaFade 24s infinite, lnCinemaKB 24s infinite;
}
.cinema .frame:nth-child(1) { animation-delay: 0s; }
.cinema .frame:nth-child(2) { animation-delay: 6s; }
.cinema .frame:nth-child(3) { animation-delay: 12s; }
.cinema .frame:nth-child(4) { animation-delay: 18s; }
@keyframes lnCinemaFade {
  0% { opacity: 0; } 2% { opacity: 1; } 23% { opacity: 1; } 27% { opacity: 0; } 100% { opacity: 0; }
}
@keyframes lnCinemaKB {
  0% { transform: scale(1.03); } 27% { transform: scale(1.08); } 100% { transform: scale(1.08); }
}
.cinema-caps { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
.cinema-caps span {
  position: absolute; left: 1.5rem; right: 1.5rem; bottom: 1.1rem; text-align: center;
  font-family: 'Lora', Georgia, serif; font-style: italic; font-size: 1.05rem; color: var(--ink);
  text-shadow: 0 1px 8px var(--sand), 0 0 3px var(--sand);
  opacity: 0; animation: lnCinemaFade 24s infinite;
}
.cinema-caps span:nth-child(1) { animation-delay: 0s; }
.cinema-caps span:nth-child(2) { animation-delay: 6s; }
.cinema-caps span:nth-child(3) { animation-delay: 12s; }
.cinema-caps span:nth-child(4) { animation-delay: 18s; }
/* ⚠ A FLAT wash over the whole frame washes the PICTURE out, not just the area
   that needs to carry text - at 72% the stills were barely visible and the hero
   read as white haze over something moving. Dark grounds get away with a flat
   scrim because the image keeps its highlights; a light one loses everything.

   So: keep the veil very light overall, and concentrate the paper only where the
   words actually sit, as an ellipse behind the text block that fades out well
   before the edges. The frame shows at full strength in the corners, the heading
   still has something to sit on, and the type carries its own shadow rather than
   relying on the ground. */
.cinema-hero {
  position: relative; z-index: 2; border-bottom: none;
  background:
    radial-gradient(58% 62% at 50% 48%,
      color-mix(in srgb, var(--sand) 76%, transparent) 0%,
      color-mix(in srgb, var(--sand) 52%, transparent) 55%,
      transparent 78%),
    linear-gradient(
      color-mix(in srgb, var(--sand) 14%, transparent),
      color-mix(in srgb, var(--sand) 30%, transparent));
}
.cinema-hero h1, .cinema-hero .tagline, .cinema-hero .lead {
  text-shadow: 0 1px 3px var(--sand), 0 0 14px var(--sand), 0 0 26px var(--sand);
}

@media (prefers-reduced-motion: reduce) {
  /* Keep the opacity cross-fade (fades are vestibular-safe), drop the zoom. */
  .cinema .frame { animation: lnCinemaFade 24s infinite; transform: none; }
  .cinema-caps span { animation: lnCinemaFade 24s infinite; }
}

/* Three-up teaser row on the home page. */
.universe-columns {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 2rem; align-items: start;
}
