> ## 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.

# Decks and the community vote

> Every team hands the jury two links, a demo deck and a presentation, and when the organizers open the vote, participants rank the other teams. The jury's own scoring is on paper, not in the application.

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={"Decks and the community vote"} drawing={"meridians"} seed={3215971213} compact={true} />

Every team hands the jury two links, a demo deck and a presentation, and when the organizers open the vote, participants rank the other teams. The jury's own scoring is on paper, not in the application.

## The jury result is on paper

The jury watches each team present live and scores on paper. The application holds no jury score and no
jury ranking. The community result is never the jury result, and nothing combines the two.

## Your team's two decks

Each team has two links of its own, each a Slideless share link the team made:

* **the demo deck**: how to try what you built, step by step, so the jury can play it alone;
* **the presentation**: the pitch you give on stage.

Set them from the command line, either one alone or both at once:

```bash theme={"theme":{"light":"rose-pine-dawn","dark":"rose-pine-moon"}}
hackathon decks link --demo https://<host>/v/<token>/ --pitch https://<host>/v/<token>/
hackathon decks show        # what the platform holds for your team
```

The team is the one of the signed-in participant. A link that is not a Slideless share link is refused.
A third link, the **vote presentation** (`hackathon decks link --vote https://<host>/v/<token>/`, made with
`/plugin:vote`), is the deck the other participants watch on the Vote page and rank: a team is a vote
candidate once it is set.
The **Teams** page shows every team's two links, `Demo deck` and `Presentation`, and `hackathon decks
gallery` prints one line per team.

## The community vote

The Vote section is off unless the organizers turn it on. When it is on, participants rank the other teams
on the **Vote** page, and `hackathon community status` prints the same.

### Who votes, who is ranked

Before the vote opens, an organizer locks who takes part: the candidate teams, and the voters, every
participant on a team. The lock is a snapshot: a later change of the roster does not change who votes or
who is ranked.

### The window

A ballot is accepted from the vote's opening to its closing, on the server's clock. A ballot that reaches
the server one instant after the close is refused.

### The ballot

* **One ballot per participant.** You rank every other candidate, best first. Your own team is listed
  apart and cannot be ranked.
* **Draft and submit.** *Save draft* keeps a ranking in progress, complete or not. *Submit ballot* needs
  every other team exactly once. A submission replaces the previous one; only the submitted ranking counts.
* **Edits until the close.** You can submit again until the vote closes; the last submission is the one that
  counts.
* **Privacy.** You see your own ballot and nobody else's. There is no live leaderboard.

From the command line: `hackathon community ballot show`, `hackathon community ballot save --ranking <ids>` and `hackathon community ballot submit --ranking <ids>`, the project ids best first.

### The scoring rule

For a submitted ballot that ranks `m` teams, the team at rank `r` (1 is the best) gets:

```text theme={"theme":{"light":"rose-pine-dawn","dark":"rose-pine-moon"}}
points = (m - r) / (m - 1), when m > 1
points = 1, when m = 1
```

A team's community score is the mean of its points over the submitted ballots that were allowed to rank it.
The members of a team cannot rank it, so they are not in its count. The number of contributing ballots is
shown beside each score. A team no submitted ballot could rank is unranked, not scored zero.

Teams are sorted by score, highest first. Equal scores share a rank (1, 1, 3).

**Example.** Three solo teams A, B and C. A ranks B then C; B ranks C then A; C ranks B then A. Each ballot
ranks two teams, so first place is worth 1 point and second place 0.

| Team | Points received | Score |
| - | - | - |
| A | 0 (from B), 0 (from C) | 0 |
| B | 1 (from A), 1 (from C) | 1 |
| C | 0 (from A), 1 (from B) | 0.5 |

**A bigger team casts more ballots.** Every participant has one ballot, so a team of three casts three
ballots and a solo team one. None of them can rank their own team.

### After the close

The vote closes at its closing time, or earlier when an organizer closes it. An organizer then calculates the
result and releases it, and it appears on the **Vote** page for every participant. A later calculation stays
unreleased until an organizer releases it.

## Generated decks

When the Vote section is on, the platform can also generate two decks per team from the frozen submission,
a jury deck for a five-minute presentation and a community deck for a quick comparison of the teams. An
organizer reviews each one and releases it; a released deck opens as a read-only view from the **Teams**
page, and a deck not released yet shows as pending. Every claim on a generated deck cites the file it comes
from, or is marked unverified, and the generation never runs the team's code.
