> ## Documentation Index
> Fetch the complete documentation index at: https://docs.antasphere.com/llms.txt
> Use this file to discover all available pages before exploring further.

# References

> A reference is a deck the workspace keeps to make other decks from. It is a deck like any other: it has versions, files, an owner and share links. What makes it a reference is one line in its AGENT.md. An agent that authors a new deck fetches the reference, reads it, and builds the new deck from what it read.

export const HeroBand = ({eyebrow, title, lede, drawing = 'latitudes', seed = 976086463, level = 0.55, compact = false, fieldSeed = 1872629950}) => {
  const box = useRef(null);
  const groundRef = useRef(null);
  const bodyRef = useRef(null);
  const canvasRef = useRef(null);
  useEffect(() => {
    const boxEl = box.current;
    const groundEl = groundRef.current;
    const bodyEl = bodyRef.current;
    const canvas = canvasRef.current;
    if (!boxEl || !groundEl || !bodyEl || !canvas) return;
    const TAU = Math.PI * 2;
    const DPR = Math.min(window.devicePixelRatio || 1, 2);
    const isDark = () => document.documentElement.classList.contains('dark');
    const PALETTES = {
      'umber-field': {
        light: true,
        base: '#ECE4D6',
        hues: ['#FAF6EE', '#F6F0E4', '#ECE4D6', '#D9C6AC', '#C9B193', '#F8F3EA', '#B89E80']
      },
      'umber-field-dark': {
        light: false,
        base: '#2F2B27',
        hues: ['#2B2722', '#34302C', '#3E3A35', '#4C4743', '#5A5651', '#302C28', '#6A6560']
      }
    };
    const LOW_W = 116;
    function mulberry32(s) {
      let a = s >>> 0;
      return function () {
        a |= 0;
        a = a + 0x6d2b79f5 | 0;
        let t = Math.imul(a ^ a >>> 15, 1 | a);
        t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
        return ((t ^ t >>> 14) >>> 0) / 4294967296;
      };
    }
    function buildBlobs(s, palName) {
      const pal = PALETTES[palName];
      const rnd = mulberry32(s);
      const count = 7 + Math.floor(rnd() * 4);
      const blobs = [];
      for (let i = 0; i < count; i++) {
        blobs.push({
          color: pal.hues[Math.floor(rnd() * pal.hues.length)],
          x: rnd(),
          y: rnd(),
          r: 0.22 + rnd() * 0.4,
          alpha: 0.75 + rnd() * 0.25,
          px: 0.5 + rnd() * 1.5,
          py: 0.5 + rnd() * 1.5,
          ax: 0.02 + rnd() * 0.05,
          ay: 0.02 + rnd() * 0.05,
          ph: rnd() * TAU
        });
      }
      return blobs;
    }
    function renderLow(blobs, palName, W, H) {
      const pal = PALETTES[palName];
      const lw = LOW_W;
      const lh = Math.max(8, Math.round(LOW_W * H / W));
      const low = document.createElement('canvas');
      low.width = lw;
      low.height = lh;
      const c = low.getContext('2d');
      c.fillStyle = pal.base;
      c.fillRect(0, 0, lw, lh);
      blobs.forEach(b => {
        const x = (b.x + Math.sin(b.ph) * b.ax) * lw;
        const y = (b.y + Math.cos(b.ph) * b.ay) * lh;
        const r = b.r * lw;
        const g = c.createRadialGradient(x, y, 0, x, y, r);
        g.addColorStop(0, b.color);
        g.addColorStop(1, b.color.slice(0, 7) + '00');
        c.fillStyle = g;
        c.globalAlpha = b.alpha;
        c.beginPath();
        c.arc(x, y, r, 0, TAU);
        c.fill();
        c.globalAlpha = 1;
      });
      return low;
    }
    function noiseTile(size, amp) {
      const c = document.createElement('canvas');
      c.width = c.height = size;
      const ctx = c.getContext('2d');
      const img = ctx.createImageData(size, size);
      const d = img.data;
      for (let i = 0; i < d.length; i += 4) {
        const v = 128 + (Math.random() - 0.5) * 255 * amp;
        d[i] = d[i + 1] = d[i + 2] = v;
        d[i + 3] = 255;
      }
      ctx.putImageData(img, 0, 0);
      return c;
    }
    const OVER = 0.24;
    let sheet = null;
    let sheetKey = '';
    function makeSheet(W, H) {
      const key = isDark() ? 'umber-field-dark' : 'umber-field';
      const sw = Math.round(W * (1 + OVER * 2));
      const sh = Math.round(H * (1 + OVER * 2));
      const c = document.createElement('canvas');
      c.width = sw;
      c.height = sh;
      const ctx = c.getContext('2d');
      const low = renderLow(buildBlobs(fieldSeed, key), key, sw, sh);
      ctx.imageSmoothingEnabled = true;
      ctx.imageSmoothingQuality = 'high';
      ctx.filter = 'blur(' + Math.max(4, Math.round(sw * 0.018)) + 'px)';
      ctx.drawImage(low, -sw * 0.06, -sh * 0.06, sw * 1.12, sh * 1.12);
      ctx.filter = 'none';
      sheet = c;
      sheetKey = key + ':' + sw + 'x' + sh;
    }
    let slide = {
      x: 0,
      y: 0
    };
    function paintGround() {
      const W = Math.round(groundEl.offsetWidth * DPR);
      const H = Math.round(groundEl.offsetHeight * DPR);
      if (W < 2 || H < 2) return;
      if (groundEl.width !== W || groundEl.height !== H) {
        groundEl.width = W;
        groundEl.height = H;
      }
      const key = isDark() ? 'umber-field-dark' : 'umber-field';
      const sw = Math.round(W * (1 + OVER * 2));
      const sh = Math.round(H * (1 + OVER * 2));
      if (!sheet || sheetKey !== key + ':' + sw + 'x' + sh) makeSheet(W, H);
      const ctx = groundEl.getContext('2d');
      const dx = Math.max(-W * OVER, Math.min(W * OVER, slide.x * DPR));
      const dy = Math.max(-H * OVER, Math.min(H * OVER, slide.y * DPR));
      ctx.clearRect(0, 0, W, H);
      ctx.drawImage(sheet, -W * OVER + dx, -H * OVER + dy);
    }
    function turn(p, pitch, yaw) {
      const ct = Math.cos(pitch);
      const st = Math.sin(pitch);
      const cp = Math.cos(yaw);
      const sp = Math.sin(yaw);
      const y2 = p.y * ct - p.z * st;
      const z2 = p.y * st + p.z * ct;
      return {
        x: p.x * cp + z2 * sp,
        y: y2,
        z: -p.x * sp + z2 * cp
      };
    }
    function ringAround(axis, h, samples) {
      const r = Math.sqrt(Math.max(0, 1 - h * h));
      const ref = Math.abs(axis.y) < 0.9 ? {
        x: 0,
        y: 1,
        z: 0
      } : {
        x: 1,
        y: 0,
        z: 0
      };
      let ux = axis.y * ref.z - axis.z * ref.y;
      let uy = axis.z * ref.x - axis.x * ref.z;
      let uz = axis.x * ref.y - axis.y * ref.x;
      const ul = Math.hypot(ux, uy, uz);
      ux /= ul;
      uy /= ul;
      uz /= ul;
      const vx = axis.y * uz - axis.z * uy;
      const vy = axis.z * ux - axis.x * uz;
      const vz = axis.x * uy - axis.y * ux;
      const out = [];
      for (let k = 0; k <= samples; k++) {
        const th = k / samples * TAU;
        const c = Math.cos(th) * r;
        const s = Math.sin(th) * r;
        out.push({
          x: axis.x * h + c * ux + s * vx,
          y: axis.y * h + c * uy + s * vy,
          z: axis.z * h + c * uz + s * vz
        });
      }
      return out;
    }
    function strokeRuns(f, v, pts, near, far) {
      const {ctx} = f;
      let open = false;
      let side = false;
      const close = () => {
        if (!open) return;
        const a = side ? near : far;
        if (a > 0) {
          ctx.strokeStyle = f.ink(a);
          ctx.stroke();
        }
        open = false;
      };
      for (const p of pts) {
        const sx = v.cx + p.x * v.R;
        const sy = v.cy - p.y * v.R;
        const front = p.z >= 0;
        if (open && front !== side) {
          ctx.lineTo(sx, sy);
          close();
        }
        if (!open) {
          ctx.beginPath();
          ctx.moveTo(sx, sy);
          open = true;
          side = front;
        } else {
          ctx.lineTo(sx, sy);
        }
      }
      close();
    }
    function outline(f, v, a, w = 0.8) {
      f.ctx.lineWidth = Math.max(1, f.dpr * w);
      f.ctx.strokeStyle = f.ink(a);
      f.ctx.beginPath();
      f.ctx.arc(v.cx, v.cy, v.R, 0, TAU);
      f.ctx.stroke();
    }
    function view(f, scale = 0.42) {
      return {
        cx: f.W / 2,
        cy: f.H / 2,
        R: Math.min(f.W, f.H) * scale
      };
    }
    const up = {
      x: 0,
      y: 1,
      z: 0
    };
    function latitudes(s) {
      const squash = 0.26 + mulberry32(s)() * 0.12;
      const lean = Math.asin(squash);
      const N = 13;
      const rows = Array.from({
        length: N - 1
      }, (_, i) => (i + 1) / N * 2 - 1);
      const rings = rows.map(h => ringAround(up, h, 72));
      return f => {
        const v = view(f);
        outline(f, v, 0.4);
        f.ctx.lineWidth = Math.max(1, f.dpr * 0.8);
        const pitch = lean + f.pitch;
        rings.forEach((ring, i) => {
          const h = Math.abs(rows[i]);
          strokeRuns(f, v, ring.map(p => turn(p, pitch, f.yaw)), 0.2 + h * 0.12, 0.05 + h * 0.03);
        });
        let pole = turn(up, pitch, f.yaw);
        if (pole.z < 0) pole = {
          x: -pole.x,
          y: -pole.y,
          z: -pole.z
        };
        f.ctx.fillStyle = f.ink(0.5);
        f.ctx.beginPath();
        f.ctx.arc(v.cx + pole.x * v.R, v.cy - pole.y * v.R, Math.max(1.5, f.dpr * 1.5), 0, TAU);
        f.ctx.fill();
      };
    }
    function meridians(s) {
      const rnd = mulberry32(s);
      const lean = 0.3 + rnd() * 0.14;
      const greats = [];
      for (let k = 0; k < 9; k++) {
        const th = k / 9 * Math.PI;
        greats.push(ringAround({
          x: Math.cos(th),
          y: 0,
          z: Math.sin(th)
        }, 0, 96));
      }
      const rows = [-0.75, -0.45, -0.15, 0.15, 0.45, 0.75];
      const parallels = rows.map(h => ringAround(up, h, 72));
      return f => {
        const v = view(f);
        outline(f, v, 0.4);
        f.ctx.lineWidth = Math.max(1, f.dpr * 0.8);
        const pitch = lean + f.pitch;
        const yaw = f.yaw + f.t * 0.05;
        greats.forEach(ring => strokeRuns(f, v, ring.map(p => turn(p, pitch, yaw)), 0.3, 0.07));
        parallels.forEach(ring => strokeRuns(f, v, ring.map(p => turn(p, pitch, yaw)), 0.17, 0.045));
      };
    }
    const GOLDEN = Math.PI * (3 - Math.sqrt(5));
    const clamp01 = v => v < 0 ? 0 : v > 1 ? 1 : v;
    function spinY(p, a) {
      const c = Math.cos(a);
      const s = Math.sin(a);
      return {
        x: p.x * c + p.z * s,
        y: p.y,
        z: -p.x * s + p.z * c
      };
    }
    function plm(l, m, x) {
      let pmm = 1;
      if (m > 0) {
        const s = Math.sqrt((1 - x) * (1 + x));
        let fact = 1;
        for (let i = 1; i <= m; i++) {
          pmm *= -fact * s;
          fact += 2;
        }
      }
      if (l === m) return pmm;
      let pmmp1 = x * (2 * m + 1) * pmm;
      if (l === m + 1) return pmmp1;
      let pll = 0;
      for (let ll = m + 2; ll <= l; ll++) {
        pll = (x * (2 * ll - 1) * pmmp1 - (ll + m - 1) * pmm) / (ll - m);
        pmm = pmmp1;
        pmmp1 = pll;
      }
      return pll;
    }
    function harmonic(s) {
      const rnd = mulberry32(s);
      const pairs = [[2, 1], [3, 2], [4, 2], [5, 3], [3, 1], [4, 3], [6, 4]];
      const [l, m] = pairs[Math.floor(rnd() * pairs.length)];
      const lean = 0.28 + rnd() * 0.5;
      const spin0 = rnd() * TAU;
      const circles = [];
      let prev = plm(l, m, -0.999);
      for (let i = 1; i <= 2000; i++) {
        const x = -0.999 + 1.998 * i / 2000;
        const cur = plm(l, m, x);
        if (prev * cur < 0) circles.push(ringAround(up, x - 0.0005, 72));
        prev = cur;
      }
      const greats = Array.from({
        length: m
      }, (_, k) => {
        const phi = (Math.PI / 2 + k * Math.PI) / m;
        return ringAround({
          x: -Math.sin(phi),
          y: 0,
          z: Math.cos(phi)
        }, 0, 96);
      });
      return f => {
        const v = view(f, 0.44);
        outline(f, v, 0.35, 1);
        const spin = spin0 + f.t * 0.05;
        const pitch = lean + f.pitch;
        const place = p => turn(spinY(p, spin), pitch, f.yaw);
        f.ctx.lineWidth = Math.max(1, f.dpr * 0.85);
        for (const c of circles) strokeRuns(f, v, c.map(place), 0.46, 0.1);
        for (const g of greats) strokeRuns(f, v, g.map(place), 0.46, 0.1);
      };
    }
    function lattice(s) {
      const rnd = mulberry32(s);
      const tilt = 0.25 + rnd() * 0.5;
      const spin0 = rnd() * TAU;
      let built = null;
      const build = n => {
        const pts = [];
        for (let k = 0; k < n; k++) {
          const z = 1 - (2 * k + 1) / n;
          const r = Math.sqrt(Math.max(0, 1 - z * z));
          pts.push({
            x: r * Math.cos(k * GOLDEN),
            y: r * Math.sin(k * GOLDEN),
            z
          });
        }
        const edges = [];
        const seen = new Set();
        for (let k = 0; k < n; k++) {
          const near = pts.map((b, j) => ({
            j,
            d: (pts[k].x - b.x) ** 2 + (pts[k].y - b.y) ** 2 + (pts[k].z - b.z) ** 2
          })).filter(o => o.j !== k).sort((p, q) => p.d - q.d).slice(0, 3);
          for (const e of near) {
            const key = Math.min(k, e.j) + ':' + Math.max(k, e.j);
            if (seen.has(key)) continue;
            seen.add(key);
            edges.push([k, e.j]);
          }
        }
        return {
          n,
          pts,
          edges
        };
      };
      return f => {
        const v = view(f);
        const n = Math.min(f.W, f.H) < 560 ? 140 : 260;
        if (!built || built.n !== n) built = build(n);
        const proj = built.pts.map(p => turn(p, tilt + f.pitch, spin0 + f.t * 0.04 + f.yaw));
        const {ctx} = f;
        ctx.lineWidth = Math.max(1, f.dpr * 0.6);
        for (const [i, j] of built.edges) {
          const a = proj[i];
          const b = proj[j];
          const depth = (a.z + b.z) / 2;
          if (depth < -0.12) continue;
          ctx.strokeStyle = f.ink(0.05 + 0.2 * clamp01((depth + 1) / 2));
          ctx.beginPath();
          ctx.moveTo(v.cx + a.x * v.R, v.cy - a.y * v.R);
          ctx.lineTo(v.cx + b.x * v.R, v.cy - b.y * v.R);
          ctx.stroke();
        }
        for (const p of proj) {
          const d = clamp01((p.z + 1) / 2);
          ctx.fillStyle = f.ink(0.14 + 0.6 * d * d);
          ctx.beginPath();
          ctx.arc(v.cx + p.x * v.R, v.cy - p.y * v.R, Math.max(0.8, f.dpr * (0.5 + 1.5 * d)), 0, TAU);
          ctx.fill();
        }
        outline(f, v, 0.14);
      };
    }
    const BUILDERS = {
      latitudes,
      meridians,
      harmonic,
      lattice
    };
    const solid = (BUILDERS[drawing] || latitudes)(seed);
    let held = {
      a: 0,
      b: 0,
      t: 0
    };
    function paintBody() {
      const W = Math.round(canvas.offsetWidth * DPR);
      const H = Math.round(canvas.offsetHeight * DPR);
      if (W < 2 || H < 2) return;
      if (canvas.width !== W || canvas.height !== H) {
        canvas.width = W;
        canvas.height = H;
      }
      const ctx = canvas.getContext('2d');
      ctx.clearRect(0, 0, W, H);
      const rgb = isDark() ? '247,244,236' : '28,25,21';
      const ink = a => 'rgba(' + rgb + ',' + Math.min(0.96, a).toFixed(3) + ')';
      solid({
        ctx,
        W,
        H,
        dpr: DPR,
        ink,
        pitch: held.b,
        yaw: held.a,
        t: held.t
      });
    }
    const still = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    let stop = () => {};
    if (!still) {
      const aim = {
        x: 0,
        y: 0
      };
      const onMove = e => {
        if (e.pointerType === 'touch') return;
        const r = boxEl.getBoundingClientRect();
        const clamp = v => Math.max(-1, Math.min(1, v));
        aim.x = clamp((e.clientX - (r.left + r.width / 2)) / (window.innerWidth / 2));
        aim.y = clamp((e.clientY - (r.top + r.height / 2)) / (window.innerHeight / 2));
      };
      const onLeave = () => {
        aim.x = 0;
        aim.y = 0;
      };
      window.addEventListener('pointermove', onMove, {
        passive: true
      });
      document.documentElement.addEventListener('pointerleave', onLeave);
      let seen = false;
      const io = new IntersectionObserver(([e]) => {
        seen = e.isIntersecting;
      });
      io.observe(boxEl);
      const at = {
        x: 0,
        y: 0,
        vx: 0,
        vy: 0
      };
      const t0 = performance.now();
      let last = t0;
      let rafId = requestAnimationFrame(function tick(now) {
        rafId = requestAnimationFrame(tick);
        const dt = Math.min(0.05, (now - last) / 1000);
        last = now;
        if (!seen) return;
        at.vx += ((aim.x - at.x) * 18 - at.vx * 8.5) * dt;
        at.vy += ((aim.y - at.y) * 18 - at.vy * 8.5) * dt;
        at.x += at.vx * dt;
        at.y += at.vy * dt;
        const t = (now - t0) / 1000;
        const a = at.x * 0.4 + Math.sin(t * 0.21) * 0.07;
        const away = 0.2;
        const b = at.y < 0 ? -away * Math.tanh(-at.y * 0.3 / away) : at.y * 0.3;
        held = {
          a,
          b,
          t
        };
        const radius = bodyEl.offsetWidth * 0.42;
        slide = {
          x: -a * radius,
          y: -b * radius * 0.6
        };
        paintGround();
        const sway = compact ? 0.5 : 0.8;
        const dx = (Math.sin(t * 0.43) * 6 + Math.sin(t * 0.19 + 1.3) * 4) * sway;
        const dy = (Math.cos(t * 0.37) * 8 + Math.sin(t * 0.23) * 4) * sway;
        const lift = 1 + Math.sin(t * 0.31 + 0.6) * 0.016;
        bodyEl.style.transform = 'translate3d(' + dx.toFixed(2) + 'px, ' + dy.toFixed(2) + 'px, 0) scale(' + lift.toFixed(4) + ')';
        paintBody();
      });
      stop = () => {
        cancelAnimationFrame(rafId);
        io.disconnect();
        window.removeEventListener('pointermove', onMove);
        document.documentElement.removeEventListener('pointerleave', onLeave);
      };
    }
    const grainEl = boxEl.querySelector('.ant-hero-grain');
    if (grainEl && !grainEl.style.backgroundImage) {
      grainEl.style.backgroundImage = 'url(' + noiseTile(512, 0.9).toDataURL() + ')';
      grainEl.style.backgroundSize = 512 / DPR + 'px';
    }
    const repaint = () => {
      sheet = null;
      paintGround();
      paintBody();
    };
    const ro = new ResizeObserver(repaint);
    ro.observe(boxEl);
    const mo = new MutationObserver(repaint);
    mo.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['class']
    });
    repaint();
    return () => {
      stop();
      ro.disconnect();
      mo.disconnect();
    };
  }, [drawing, seed, compact, fieldSeed]);
  return <div className={'ant-hero' + (compact ? ' compact' : '')} data-hero ref={box} style={{
    '--level': level
  }}>
      <canvas className="ant-hero-ground" ref={groundRef} aria-hidden="true" />
      <div className="ant-hero-grain" aria-hidden="true" />
      <div className="ant-hero-shade" aria-hidden="true" />
      <div className="ant-hero-drawing" aria-hidden="true">
        <div className="ant-hero-body" ref={bodyRef}>
          <canvas ref={canvasRef} />
        </div>
      </div>
      <div className="ant-hero-words">
        {eyebrow ? <p className="ant-hero-eyebrow">{eyebrow}</p> : null}
        <h1 className="ant-hero-title">{title}</h1>
        {lede && !compact ? <p className="ant-hero-lede">{lede}</p> : null}
      </div>
    </div>;
};

<HeroBand eyebrow={"Concepts"} title={"References"} drawing={"latitudes"} seed={2364372816} compact={true} />

A reference is a deck the workspace keeps to make other decks from. It is a deck like any other: it
has versions, files, an owner and share links. What makes it a reference is one line in its
`AGENT.md`. An agent that authors a new deck fetches the reference, reads it, and builds the new deck
from what it read.

There are two types today:

* **A brand** holds how the company looks and sounds: fonts, colours, backgrounds, shapes, motion
  and voice. Its pages show the brand applied, and its files carry the logo and the fonts.
* **A template** holds the structure of a recurring deck: what the deck is for, which pages it has
  and how to fill them. Its pages are the model to copy.

`brand` and `template` are names for the same thing underneath. The API calls it `reference`.

## The type comes from the file

The reserved `AGENT.md` at the root of the bundle (see
[Deck self-description](/slideless/agents/deck-self-description)) may start with a frontmatter: a YAML
block between two `---` lines, at the very top of the file. A frontmatter whose `type` is `Brand` or
`Template` makes the deck a reference of that type. The match ignores case, and the API returns the
type in lowercase.

Every reference carries the same first fields:

| Field         | What it holds                                  |
| ------------- | ---------------------------------------------- |
| `type`        | `Brand` or `Template`                          |
| `title`       | The name of the reference                      |
| `description` | One or two sentences on what it is             |
| `tags`        | A list of free words                           |
| `timestamp`   | When the content was last revised, as ISO 8601 |

Then come the fields of the type. A brand has `fonts`, `colors`, `background`, `shape`, `motion` and
`voice`. A template has `purpose`, `pages` and `fill`. Slideless stores every field as you wrote it.
It reads `type` and nothing else, so the shape inside each field is yours to choose.

The text under the frontmatter is the prose an agent reads. The frontmatter says what can be said in
values. The prose says the rest: the judgement calls, the things never to do, where each file is.

The frontmatter must close within the first 16 KB of the file, and it must stay under 16 KB once
parsed.

### A brand

```markdown theme={null}
---
type: Brand
title: Northwind Freight brand
description: The look and the voice of every Northwind Freight deck.
tags: [brand, northwind]
timestamp: 2026-09-12T09:00:00Z
fonts:
  heading:
    family: Fraunces
    weights: [600, 700]
    source: assets/fonts/fraunces.woff2
  body:
    family: Inter
    weights: [400, 500]
    source: assets/fonts/inter.woff2
  mono:
    family: JetBrains Mono
    weights: [400]
    source: assets/fonts/jetbrains-mono.woff2
colors:
  - name: Harbour
    hex: '#0B3954'
    role: primary, headings and the cover background
  - name: Signal
    hex: '#FF6B35'
    role: accent, one element per page at most
  - name: Chalk
    hex: '#F7F5F0'
    role: page background
  - name: Ink
    hex: '#1B1B1E'
    role: body text
  - name: Tide
    hex: '#8FB8C9'
    role: chart fills and secondary surfaces
background:
  default: Chalk, flat
  cover: Harbour, flat, with the white logo bottom left
  never: gradients, photographs behind text
shape:
  radius: 4px
  border: 1px solid Ink at 12% opacity
  logo: assets/logo.svg
  logoInverse: assets/logo-white.svg
  logoClearSpace: the height of the N on every side
motion:
  transitions: fade, 200ms, ease-out
  never: slide-in text, bouncing, parallax
voice:
  tone: plain, direct, numbers before adjectives
  person: we
  avoid: [seamless, world-class, synergy]
  example: We moved 41,000 containers in Q2, 6% more than in Q1.
---

# Northwind Freight brand

This deck is the brand of Northwind Freight. Read it before you build a deck for us, and copy the
values above as they are.

Page 1 is the cover as we use it. Page 2 shows the palette with each colour in its role. Page 3
shows the type scale. Page 4 shows a chart drawn the way we draw them: Tide for the series, Signal
for the one figure we want read first.

The logo files are `assets/logo.svg` and `assets/logo-white.svg`. Use them as they are. Do not
redraw the logo, do not recolour it, and keep the clear space around it.

Signal is loud on purpose. One Signal element per page is the rule, and a page with none is fine.

Write the way the example sentence reads. A reader should find the number in the first five words.
```

### A template

```markdown theme={null}
---
type: Template
title: Quarterly business review
description: The deck account managers present to a customer at the end of each quarter.
tags: [template, qbr, customer]
timestamp: 2026-09-15T14:30:00Z
purpose: >
  Show a customer what we delivered in the quarter, what went wrong, and what we propose for the
  next one. Presented live in 30 minutes, then left with the customer as a link.
pages:
  - Cover. Customer name, the quarter, the account manager.
  - The quarter in three figures. Volume shipped, on-time rate, claims.
  - Volume by lane. One bar chart, this quarter against the last.
  - On-time performance. One line chart by week, with the contract target drawn as a line.
  - Incidents. One row per incident, with its cause, its cost and what changed after it.
  - Next quarter. Up to three proposals, each with its expected effect.
  - Questions. The contact details of the account team.
fill:
  data: Put the figures in downloads/figures.csv and draw the charts from that file.
  keep: The page order, the chart types, the footer.
  change: Every figure, every customer name, the incidents, the proposals.
  length: Seven pages. Remove the incidents page only when the quarter had none.
  language: The customer's language. Keep the page titles short enough for one line.
---

# Quarterly business review

This deck is the model for a quarterly business review. Copy its pages, keep their order, and
replace the content with the customer's quarter.

The sample figures belong to an invented customer. Never leave one of them in a real deck: every
number on every page is replaced, or its page is removed.

The three figures on page 2 are the ones the customer's contract names. When a contract names
different ones, use those and keep three.

The incidents page is the one customers read most closely. State the cause in plain words. A page
that says nothing went wrong, when something did, costs more than the incident.

Apply the workspace's brand on top of this template. The template carries structure, the brand
carries the look.
```

## A frontmatter that cannot be used

The classification never refuses a push. When the frontmatter is malformed, too large, or names a
type Slideless does not know, the push succeeds and the deck stays an ordinary deck. The push answer
carries one sentence that says what was unusable, in `version.referenceWarning`, and the CLI prints
it. For example:

```json theme={null}
{
  "version": {
    "version": 4,
    "reference": null,
    "referenceWarning": "AGENT.md frontmatter names the type \"Palette\", which is not a known reference type (brand, template); the deck was saved as an ordinary deck."
  }
}
```

Fix the file and push again. The next version is classified afresh.

An `AGENT.md` with no frontmatter, or with a frontmatter that has no `type`, is an ordinary briefing.
It raises no warning.

## Existing decks

A deck is classified when a version is pushed. A deck that already exists stays an ordinary deck
until its next push, even when its `AGENT.md` already carries a usable frontmatter. Push it once and
it becomes a reference.

## What the API returns

A deck carries three fields for this, on every read:

* `reference`: the frontmatter of the current version as an object, with `type` in lowercase. It is
  `null` on an ordinary deck.
* `audience`: `private` or `workspace`.
* `defaultReference`: `true` or `false`.

Each version carries its own `reference` and `referenceWarning`, so the history shows what each push
declared.

## The audience

The audience says who in the workspace reads a reference. It is a property of the deck. It is never
a field of the frontmatter, so nobody changes it by editing a file.

* `private` is the default. The reference follows the ordinary deck rule: its owner, the workspace's
  admins and owners, and the collaborators invited on it ([Workspaces](/slideless/concepts/workspaces)).
* `workspace` opens the reference to every member of the workspace. They read the deck, its versions
  and its files. Nobody gains the right to push to it or to change it.
  Every version opens, the ones pushed before the switch included. Look at the history before you
  publish a deck that started as something else.

Guests do not read a workspace reference through the audience. A guest is someone whose only access
is a collaborator invitation on one deck, and that invitation stays the way to show an outsider a
reference.

Whoever administers the deck sets the audience: its owner, or a workspace admin or owner.

```http theme={null}
PATCH /api/v1/presentations/{id}
Content-Type: application/json

{ "audience": "workspace" }
```

The answer is the updated deck. The refusals:

| Answer                  | When                                                                  |
| ----------------------- | --------------------------------------------------------------------- |
| `422 not_a_reference`   | The deck is an ordinary deck. Only a reference has an audience.       |
| `403`                   | You read the deck but do not administer it.                           |
| `404`                   | You cannot read the deck.                                             |
| `409 default_reference` | The deck is the default of its type. Clear the default first (below). |

## The default reference

A workspace has at most one default reference per type: one default brand, one default template. The
default is the one an agent takes when nobody names another.

A workspace admin or owner sets it:

```http theme={null}
PATCH /api/v1/presentations/{id}
Content-Type: application/json

{ "defaultReference": true }
```

The rules:

* The reference must have the `workspace` audience. On a private reference the call answers
  `409 audience_private`. A default that most members cannot read would be of no use to them.
* Setting a default clears the previous default of that type. There is no moment with two.
* `{ "defaultReference": false }` clears it. The workspace then has no default of that type.
* A default reference cannot return to `private` while it is the default
  (`409 default_reference`). Clear the default, then change the audience.
* Deleting a default reference is allowed. The workspace then has no default of that type.
* On an ordinary deck the call answers `422 not_a_reference`.

## A project's brand

A [project](/slideless/concepts/projects) can name one brand of its own, chosen among the references linked to it. An
agent authoring a deck for that project reads the project's brand instead of the workspace's default.
A manager of the project sets it, and the reference has to be linked to the project first. When a
push takes the `type: Brand` line out of that reference's `AGENT.md`, it stops being a brand, so it
stops being the project's brand at the same moment.

## When a reference stops being one

Each push classifies the deck again, from the new version's `AGENT.md`.

* **The new version has no usable frontmatter.** The deck becomes an ordinary deck in that same
  push. Its audience returns to `private` and it stops being the default. A later push that brings
  the frontmatter back makes it a reference again, but a private one: someone has to choose the
  `workspace` audience again. A deck never reopens to the workspace through a file alone.
* **The new version changes the type**, from brand to template or the other way. The deck keeps its
  audience and stops being the default, since it was the default of a type it no longer has.

The push answer says so. When a push takes the audience back to `private` or drops the default, the
version's `referenceWarning` carries a sentence that names what the deck lost, after the sentence on
the frontmatter when there is one. The CLI prints it, so the person who pushed knows at once.

## Duplicating a reference

A duplicate copies one version of a deck. When that version is a reference, the duplicate is a
reference of the same type. It is private and it is never the default, whatever the original was.

## Listing references

References leave the ordinary list. `GET /api/v1/presentations` with no `type` returns ordinary
decks only. The `type` parameter lists references instead:

```http theme={null}
GET /api/v1/presentations?type=brand
GET /api/v1/presentations?type=template
GET /api/v1/presentations?type=reference
```

`brand` and `template` list the references of that type, and `reference` lists every reference. The
list holds what you can read: your own references, the ones you collaborate on, and every reference
with the `workspace` audience. Admins and owners see them all.

Add `default=true` to keep only the default references:

```http theme={null}
GET /api/v1/presentations?type=brand&default=true
GET /api/v1/presentations?default=true
```

The first call answers "which deck is the house brand" in one request. It returns one deck, or none
when the workspace has no default brand. The second returns the default of every type: `default=true`
with no `type` means `type=reference`.

Agents connected over MCP have two tools:

* `slideless_list_references` takes a `type` and lists the references of that type.
* `slideless_get_default_reference` takes a `type` and returns the workspace's default of that type.

## From the command line

The CLI carries the same operations, in one real family and two shortcuts:
`slideless reference <verb>` takes `--type brand|template` where the type
matters, and `slideless brand` and `slideless template` are the same verbs with
the type already chosen. Eight verbs: `list`, `pull`, `new`, `push`, `publish`,
`unpublish`, `default`, `start`
([References: brand, template](/slideless/agents/cli#references-brand-template)).

The everyday sequence:

* An admin scaffolds a folder with [`slideless brand new`](/slideless/agents/cli#references-brand-template),
  fills its `AGENT.md` and its pages, and pushes it with `slideless brand push`.
  That push is what classifies the deck as a brand, and it names the new deck
  after the frontmatter's `title:` line.
* The same admin opens it with `slideless brand publish`, so every member of the
  workspace reads it, and makes it the house brand with `slideless brand default`.
* A member authoring a deck runs `slideless brand pull` with no name, which
  fetches the workspace's default into `.slideless/brand/` beside the deck, and
  reads `.slideless/brand/AGENT.md` before writing a page.
* A deck that follows a template starts as a copy of it:
  `slideless template start <ref> ./new-deck` writes a fresh folder with the
  `type:` line removed, so the deck is an ordinary deck.
* The push records what the deck was made from:
  [`slideless push --brand <ref>`](/slideless/agents/cli#references-brand-template)
  and `--template <ref>` write the `references` entries above, and a push with
  neither flag records whatever `.slideless/brand/` and `.slideless/template/`
  were pulled from.

## Reusing a reference's files

A reference's files follow the reference's rule: whoever reads the reference reads its files. So a
member who reads a workspace brand can put its logo in their own deck without uploading it again.
The push asks the instance which files it is missing, and the instance answers that the logo is
already present. The bytes are stored once in the workspace ([Versions](/slideless/concepts/versions)).

This holds for as long as you read the reference. A file of a reference you cannot read counts as
missing, and the push uploads it.

## Provenance

A deck made from references records them in its `metadata`, under `references`:

```json theme={null}
{
  "references": [
    { "type": "brand", "id": "0b7e2c1a-5d8f-4a36-9c41-7f2e6d3b9a10", "version": 3 },
    { "type": "template", "id": "c4a1f9e2-3b6d-4e87-a2f5-1d9c8b7e6f04", "version": 7 }
  ]
}
```

Each entry names the type, the reference's deck id and the version that was read. The version
matters: a reference keeps changing, and the entry says which state of it the deck came from.
`metadata` is replaced whole on a `PATCH`, so keep `references` when you change another key
([Deck self-description](/slideless/agents/deck-self-description#metadata)).

## Nothing is applied automatically

Slideless never changes a deck because a reference exists. A default brand does not restyle the
workspace's decks, and a new version of a template does not touch the decks made from it. A
reference is content that an agent fetches and reads. The deck that comes out is whatever that agent
wrote, and it stays as pushed.
