// books2.jsx — the eleven-book cycle. Every title, theme, status, sound and
// flower name below is quoted from the locked master documents. Do not add
// loglines, blurbs, dates or descriptions here.
//
// TO BLOOM A BOOK: flip `released: false` → `true`. Nothing else changes —
// the garden mark renders from `released` + `flowerHex`. Books with a null
// flowerHex render no mark: canon names those flowers but their hex values
// are assigned at art production and are not yet ruled.
//
// Spine cloth colours are muted bookbinding tones, not literal flower colours.
// `ink` is the numeral colour and is verified ≥4.5:1 against its own spine —
// adjust `ink`, never the spine.

const CREAM_INK = '#FFF6E0';

const BOOKS = [
  { n: 'I',    title: 'The First Mistake',         theme: 'jealousy and tawbah',
    status: 'Test edition available now', sound: null,                play: false,
    released: true,  flowerName: 'yellow',              flowerHex: '#E8B93A',
    color: '#8A6A22', ink: CREAM_INK, w: 40, h: 158 },

  { n: 'II',   title: 'One Letter of Light',       theme: 'patience in learning',
    status: 'In production', sound: 'Scritch. Scratch.',                play: true,
    released: false, flowerName: 'marigold orange',     flowerHex: null,
    color: '#9A5A24', ink: CREAM_INK, w: 32, h: 132 },

  { n: 'III',  title: 'The Ship on Dry Land',      theme: 'sabr — perseverance',
    status: 'In production', sound: 'Pit. Pat. Pit-pat.',               play: true,
    released: false, flowerName: 'desert poppy',        flowerHex: null,
    color: '#96442F', ink: CREAM_INK, w: 34, h: 140 },

  { n: 'IV',   title: 'Who Moves the Stars?',      theme: 'the courage to ask',
    status: 'In production', sound: 'Click… cliiick',                   play: false,
    released: false, flowerName: 'blue forget-me-nots', flowerHex: null,
    color: '#3F5C74', ink: CREAM_INK, w: 31, h: 126 },

  { n: 'V',    title: 'The Dream of Eleven Stars', theme: 'forgiveness frees both hearts',
    status: 'In production', sound: null,                               play: false,
    released: false, flowerName: 'pink star-flowers',   flowerHex: null,
    color: '#8E4A58', ink: CREAM_INK, w: 35, h: 144 },

  { n: 'VI',   title: 'Still Here',                theme: 'hope is not being alone',
    status: 'In production', sound: 'rattle-rattle',                    play: false,
    released: false, flowerName: 'lavender',            flowerHex: null,
    color: '#6E5F84', ink: CREAM_INK, w: 30, h: 122 },

  { n: 'VII',  title: 'The Knot',                  theme: 'truth with the voice you have',
    status: 'In production', sound: 'thump-thump',                      play: false,
    released: false, flowerName: 'deep violet irises', flowerHex: null,
    color: '#4A3A63', ink: CREAM_INK, w: 32, h: 134 },

  { n: 'VIII', title: 'The Smallest Voice',        theme: 'gifts protect the smallest',
    status: 'In production', sound: 'tik-tik-tik',                      play: false,
    released: false, flowerName: 'coral bells',         flowerHex: null,
    color: '#A05A4C', ink: CREAM_INK, w: 36, h: 146 },

  { n: 'IX',   title: 'The Dark That Listened',    theme: 'turning around inside the dark',
    status: 'In production', sound: 'Thump. Thump.',                    play: false,
    released: false, flowerName: 'white moonflowers',   flowerHex: null,
    color: '#CFC7B6', ink: '#2A1F12', w: 31, h: 130 },

  { n: 'X',    title: 'The One Who Came Closer',   theme: 'compassion goes toward',
    status: 'In production', sound: 'Scrrrp… tap',                      play: false,
    released: false, flowerName: 'the harvest bloom',   flowerHex: null,
    color: '#6F5B2E', ink: CREAM_INK, w: 33, h: 138 },

  { n: 'XI',   title: 'A Mercy to All Worlds',     theme: 'mercy to all worlds',
    status: 'In production', sound: 'the Curtain Call',                 play: false,
    released: false, flowerName: 'the completed garden', flowerHex: null,
    color: '#4E6047', ink: CREAM_INK, w: 37, h: 150 },
];

// ── Signature sound playback — Books II and III only ─────────
// Synthesised, never sampled. Context is created on the click, peak gain is
// capped at 0.15, nothing loops, and everything is disposed on modal close.
const AudioCtor = window.AudioContext || window.webkitAudioContext;
const AUDIO_OK = !!AudioCtor;

function makeNoise(ctx, dur) {
  const n = Math.max(1, Math.floor(ctx.sampleRate * dur));
  const buf = ctx.createBuffer(1, n, ctx.sampleRate);
  const d = buf.getChannelData(0);
  for (let i = 0; i < n; i++) d[i] = Math.random() * 2 - 1;
  return buf;
}

// Returns { stop, ms } or null when Web Audio is unavailable.
function playBookSound(roman) {
  if (!AUDIO_OK) return null;
  let ctx;
  try { ctx = new AudioCtor(); } catch (e) { return null; }
  if (ctx.state === 'suspended' && ctx.resume) { try { ctx.resume(); } catch (e) { /* ignore */ } }

  const out = ctx.createGain();
  out.gain.value = 1;
  out.connect(ctx.destination);

  const t0 = ctx.currentTime + 0.04;
  let end = t0;

  if (roman === 'II') {
    // Reed pen on clay: scritch – scratch – scritch-scratch. Dry and close.
    const strokes = [0, 0.34, 0.76, 0.94];
    strokes.forEach((off, i) => {
      const dur = 0.062;
      const src = ctx.createBufferSource();
      src.buffer = makeNoise(ctx, dur);
      const bp = ctx.createBiquadFilter();
      bp.type = 'bandpass';
      bp.frequency.value = i % 2 ? 2200 : 2800;
      bp.Q.value = 3.4;
      const g = ctx.createGain();
      g.gain.setValueAtTime(0.0001, t0 + off);
      g.gain.exponentialRampToValueAtTime(0.14, t0 + off + 0.012);
      g.gain.exponentialRampToValueAtTime(0.0001, t0 + off + dur);
      src.connect(bp); bp.connect(g); g.connect(out);
      src.start(t0 + off); src.stop(t0 + off + dur + 0.02);
      end = Math.max(end, t0 + off + dur + 0.06);
    });
  } else if (roman === 'III') {
    // Cosy rain on a roof, never storm: droplets over a very low noise bed.
    const bed = ctx.createBufferSource();
    bed.buffer = makeNoise(ctx, 2.0);
    const lp = ctx.createBiquadFilter();
    lp.type = 'lowpass'; lp.frequency.value = 520;
    const bg = ctx.createGain();
    bg.gain.setValueAtTime(0.0001, t0);
    bg.gain.linearRampToValueAtTime(0.02, t0 + 0.3);
    bg.gain.setValueAtTime(0.02, t0 + 1.4);
    bg.gain.linearRampToValueAtTime(0.0001, t0 + 1.98);
    bed.connect(lp); lp.connect(bg); bg.connect(out);
    bed.start(t0); bed.stop(t0 + 2.0);

    const drops = [[0.08, 760], [0.40, 520], [0.58, 880], [1.00, 430], [1.22, 690], [1.62, 560]];
    drops.forEach(function (d) {
      const off = d[0], f = d[1];
      const o = ctx.createOscillator();
      o.type = 'sine';
      o.frequency.setValueAtTime(f, t0 + off);
      o.frequency.exponentialRampToValueAtTime(f * 0.72, t0 + off + 0.09);
      const g = ctx.createGain();
      g.gain.setValueAtTime(0.0001, t0 + off);
      g.gain.exponentialRampToValueAtTime(0.11, t0 + off + 0.006);
      g.gain.exponentialRampToValueAtTime(0.0001, t0 + off + 0.12);
      o.connect(g); g.connect(out);
      o.start(t0 + off); o.stop(t0 + off + 0.16);
      end = Math.max(end, t0 + off + 0.2);
    });
    end = Math.max(end, t0 + 2.02);
  } else {
    try { ctx.close(); } catch (e) { /* ignore */ }
    return null;
  }

  const ms = Math.max(0, (end - ctx.currentTime) * 1000);
  return {
    ms: ms,
    stop: function () { try { ctx.close(); } catch (e) { /* ignore */ } },
  };
}

// Garden mark — a small geometric rosette: six short radiating petals around
// a centre. Deliberately NOT the site's four-pointed ◆, which belongs to the
// masthead and section headers. Carries the book's own flower colour.
// Renders only when a book is released AND canon has ruled its colour.
function GardenMark({ hex, name, size = 10 }) {
  if (!hex) return null;
  return (
    <span role="img" aria-label={'Bloomed — ' + name}
      style={{
        display: 'inline-block', width: size, height: size, borderRadius: '50%',
        background: 'radial-gradient(circle at 50% 50%, ' + hex + ' 0 26%, transparent 27%), '
          + 'repeating-conic-gradient(from 15deg at 50% 50%, ' + hex + ' 0deg 22deg, transparent 22deg 60deg)',
        // thin dark ring so the mark separates from its cloth without
        // altering either the flower colour or the spine colour
        boxShadow: '0 0 0 1px rgba(0,0,0,0.34)',
      }}></span>
  );
}

Object.assign(window, { BOOKS, AUDIO_OK, playBookSound, GardenMark });
