// R+D Journal content model. The journal is the "r+d" sub of material (§04),
// but unlike the other subs it renders as a blog: a reverse-chronological list
// of articles (JournalIndex) that open into a long-form reading view
// (ArticlePage). Both live in journal.jsx and load AFTER this file.
//
// ADD A POST: append one object to JOURNAL_POSTS below. Newest first is NOT
// required — the index sorts by `date` descending automatically. The post shape:
//
//   slug    : url-safe id, unique. Used by the router ({ page:'article', slug }).
//   title   : headline.
//   date    : 'YYYY-MM-DD'. Drives sort order + both date displays.
//   status  : 'wip' | 'resolved' | 'abandoned' | 'ongoing'. 'wip'/'ongoing'
//             get the warm "active" dot (see ACTIVE_STATUSES in pages-data.jsx).
//   tags    : ['casting', 'aluminum', …] — power the index tag filter.
//   summary : one–two sentence dek, shown in the list and the article hero.
//   related : optional { label, to } — a link back to a project. `to` is a
//             router route, e.g. { page:'category', cat:'material', open:'Cast Aluminum Set' }.
//   body    : ordered array of typed blocks. Read time is derived from these.
//
// BLOCK TYPES (the `type` field):
//   { type:'h',    text }                     section heading (also builds the TOC)
//   { type:'p',    text }                     paragraph. Inline **bold** and `code` supported.
//   { type:'figure', tag, caption, src? }     image + mono caption; omit src for a placeholder
//   { type:'code', lang, code }               monospace code block
//   { type:'steps', items:[ … ] }             numbered process list (items may use **/`)
//   { type:'quote', text, cite? }             pull quote
//   { type:'materials', title?, items:[[k,v]] } tools / materials spec box
//   { type:'note', text }                     callout / side note

const JOURNAL_POSTS = [
  {
    slug: 'uv-drawn-glow-castings',
    title: 'Drawing with light: a UV laser in the plotter carriage',
    date: '2026-05-20',
    status: 'wip',
    tags: ['casting', 'silicone', 'plotter', 'phosphor'],
    related: { label: 'UV-Drawn Glow Castings', to: { page: 'category', cat: 'material', open: 'UV-Drawn Glow Castings' } },
    summary: 'Bench notes from loading platinum silicone with strontium-aluminate glow powder and swapping the AxiDraw pen for a UV laser diode — so the machine plots in charge instead of ink. What settles flat, what charges, and what fades too fast.',
    body: [
      { type: 'p', text: 'The whole idea is to make a surface that *remembers* light, then draw on it with a beam instead of a pen. Cast a slab of phosphor-loaded silicone, mount a UV laser diode where the pen normally goes on the AxiDraw, and run a plot — wherever the beam dwells the powder excites and holds, so the linework blooms back as a glowing afterimage once the room goes dark. Simple to say. The two halves — the casting and the plotting — each have a floor you fall through if you guess at the numbers.' },
      { type: 'materials', title: 'Setup', items: [['Cast', 'Platinum-cure silicone'], ['Pigment', 'Strontium-aluminate phosphor'], ['Mold', '3D-printed frame, shallow'], ['Charge', '405nm UV laser on AxiDraw'], ['Source', 'Vector paths (grids, contour fields, warped mesh)']] },

      { type: 'h', text: 'Getting the pour to settle flat' },
      { type: 'p', text: 'The tiles only read if the top is dead flat and the powder is evenly suspended — any meniscus or settling shows up later as a bright rim or a dim corner once you charge it. Two things fixed it: a **shallow** frame mold so the pour is wide and thin rather than deep, and folding the phosphor into the part-B base *before* adding part-A so the powder wets out before the clock starts.' },
      { type: 'steps', items: [
        'Disperse the glow powder into the part-B silicone first and mix to a uniform slurry — it wets slowly, and if you wait until after catalyzing you will not have time.',
        'Add part-A, mix, then **degas under vacuum**. Platinum silicone tolerates it and the trapped air is what otherwise floats powder to the surface as a speckled skin.',
        'Pour into a shallow 3D-printed frame on a leveled bench. Wide and thin beats deep — depth just buries phosphor the beam never reaches.',
        'Skim the surface with a card once, then leave it. Overworking the top reintroduces bubbles right where you most want clarity.',
      ] },
      { type: 'figure', tag: 'casting', caption: 'Mixing the glow-powder silicone; tiles curing flat in their 3D-printed frames.', src: 'uploads/work/glow-silicone-bench.jpg' },
      { type: 'note', text: 'Platinum silicone is cure-inhibited by a lot of things — sulfur clays, some 3D-print resins, latex gloves. The frame molds get printed in PLA and given a day to off-gas, or the contact face stays tacky forever.' },

      { type: 'h', text: 'The laser is not a pen' },
      { type: 'p', text: 'The plotter wants to drive the head like it is inking: full speed on the draw, pen-up between paths. But charge is a function of **dwell** — how long the beam sits on a spot — not just whether the path was traced. Run it at normal pen speed and the lines barely take; the phosphor needs the beam to linger. So the real variable is feed rate, and it trades directly against line brightness.' },
      { type: 'steps', items: [
        'Slow the draw feed *way* down from ink speeds — the beam has to dwell long enough to pump the phosphor, not just pass over it.',
        'Force pen-up between every path so the laser is gated off in transit. A laser does not stop drawing when it lifts a couple millimeters the way a pen does — it has to be switched.',
        'Keep the focal distance constant. The frame holds the tile, but a warped slab drifts out of focus mid-plot and the line goes soft.',
        'Charge in the dark and photograph immediately — the afterimage starts decaying the moment the beam moves on.',
      ] },
      { type: 'figure', tag: 'plotting', caption: 'UV laser tracing vector paths across a tile in the AxiDraw carriage.', src: 'uploads/work/glow-silicone-laser.jpg' },

      { type: 'h', text: 'What it looks like in the dark' },
      { type: 'p', text: 'When it works, the plotted paths — grids, contour fields, warped meshes — bloom back out of the slab in orange, teal, and blue, fading slowly as the charge decays. The decay is the point, not a bug: the drawing has a half-life, so the same tile reads differently thirty seconds after you kill the lights than it does at the instant the plot finishes.' },
      { type: 'quote', text: 'It is the only drawing I make that erases itself while you watch it.' },

      { type: 'h', text: 'Open questions' },
      { type: 'p', text: 'Still unresolved: the brightest paths and the dimmest ones decay at noticeably different rates, so a dense plot goes muddy before a sparse one even dims. I think that is a dwell-uniformity problem — the path-ordering matters because retracing near a still-glowing line double-charges it. Next test is plotting hot regions **last** so they finish brightest, and measuring decay with a long exposure instead of my eyes. Will update.' },
      { type: 'figure', tag: 'glowing', caption: 'Plotted vector paths blooming back in the dark — orange, teal, blue, fading.', src: 'uploads/work/glow-silicone-tiles.jpg' },
    ],
  },
];

// --- derived helpers, shared by the journal views ----------------------------

// Estimate read time from the prose blocks (~200 wpm). Code is skipped — you
// scan it, you don't read it at prose speed.
function journalReadTime(post) {
  let words = 0;
  const add = (s) => { if (s) words += String(s).trim().split(/\s+/).length; };
  (post.body || []).forEach((b) => {
    if (b.type === 'p' || b.type === 'h' || b.type === 'quote') add(b.text);
    else if (b.type === 'steps') (b.items || []).forEach(add);
    else if (b.type === 'figure') add(b.caption);
    else if (b.type === 'note') add(b.text);
  });
  add(post.summary);
  return Math.max(1, Math.round(words / 200));
}

// 'YYYY-MM-DD' → { mono:'2026.05.18', long:'May 18, 2026' }
function journalDate(iso) {
  const [y, m, d] = String(iso).split('-').map((n) => parseInt(n, 10));
  const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
  const pad = (n) => String(n).padStart(2, '0');
  return {
    mono: `${y}.${pad(m)}.${pad(d)}`,
    long: `${months[(m || 1) - 1]} ${d}, ${y}`,
  };
}

// Posts newest-first.
function journalSorted() {
  return [...JOURNAL_POSTS].sort((a, b) => (a.date < b.date ? 1 : a.date > b.date ? -1 : 0));
}

window.JOURNAL_POSTS = JOURNAL_POSTS;
window.journalReadTime = journalReadTime;
window.journalDate = journalDate;
window.journalSorted = journalSorted;
