  /* The page's own typefaces (SIL OFL 1.1, the licences in fonts/): nothing
     is loaded from another address. */
  @font-face { font-family: "Uncial Antiqua"; font-style: normal; font-weight: 400; font-display: swap; src: url("fonts/UncialAntiqua-Regular.woff2") format("woff2"); }
  @font-face { font-family: "IM Fell English"; font-style: normal; font-weight: 400; font-display: swap; src: url("fonts/IMFellEnglish-Regular.woff2") format("woff2"); }
  @font-face { font-family: "IM Fell English"; font-style: italic; font-weight: 400; font-display: swap; src: url("fonts/IMFellEnglish-Italic.woff2") format("woff2"); }

  /* Layout: one scene on the films' own white ground; the page is the castle gate.
     An ode to Monty Python: the words are theirs, the animals and the foot are cheap
     paper cut-outs on purpose, after Terry Gilliam's animations. Single light look:
     the doors are filmed on white (#FDFDFD). */
  :root {
    --ground: #fdfdfd;        /* the films' own white */
    --ink: #2a211c;           /* oak in shadow */
    --ink-soft: #5d4b3f;      /* weathered oak */
    --brass: #a8711f;         /* the bell's brass */
    --brass-glow: rgba(222, 168, 64, 0.55);
    --iron: #3b3a40;          /* hinges and studs */
    --parchment: #fffaf0;     /* the taunt's scroll */
    --font-display: "Uncial Antiqua", Georgia, serif;
    --font-body: "IM Fell English", Georgia, serif;
    --font-ui: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    --display-scale: 1;
    --names-case: none;
    --names-spacing: 0.03em;
    color-scheme: light;
  }
  html, body { background: var(--ground); color: var(--ink); }
  body { font-family: var(--font-body); font-size: 19px; line-height: 1.45; }

  .page {
    max-width: 1120px;
    margin: 0 auto;
    padding-left: max(16px, env(safe-area-inset-left, 0px));
    padding-right: max(16px, env(safe-area-inset-right, 0px));
    padding-block: 28px 40px;
    display: grid;
    gap: 18px;
  }

  header { text-align: center; display: grid; gap: 6px; }
  h1 {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: calc(clamp(26px, 4.2vw, 42px) * var(--display-scale));
    letter-spacing: 0.02em;
    margin: 0;
    text-wrap: balance;
  }
  .lede { margin: 0; color: var(--ink-soft); font-size: clamp(17px, 2vw, 20px); text-wrap: balance; }

  /* THE SCENE. The box clips the films' bottom ten rows: the last one is black, and
     compression smears it into the rows above. The doors end at row 864. */
  .scene {
    position: relative;
    width: 100%;
    max-width: 100%;
    aspect-ratio: 1774 / 878;
    overflow: hidden;
  }
  .scene.is-thud { animation: thud 0.35s ease-out; }
  @keyframes thud {
    0%, 100% { transform: translate(0, 0); }
    20% { transform: translate(-3px, 2px); }
    40% { transform: translate(3px, -2px); }
    60% { transform: translate(-2px, 1px); }
    80% { transform: translate(1px, 0); }
  }
  .scene video {
    position: absolute;
    left: 0; top: 0;
    width: 100%;
    height: auto;
    display: block;
    pointer-events: none;
  }

  /* The bells: buttons laid over the brass plates in the film (measured on frame 0:
     left plate x 14–167, right plate x 1609–1762, y 272–534, of 1774 × 888). */
  .bell {
    position: absolute;
    z-index: 3;
    top: 30.6%;
    height: 29.6%;
    width: 8.7%;
    border: 0;
    padding: 0;
    background: transparent;
    border-radius: 12% / 7%;
    cursor: pointer;
  }
  .bell::after {           /* a press area of at least 44 px, also on a phone */
    content: "";
    position: absolute;
    left: 50%; top: 50%;
    width: max(100%, 44px);
    height: max(100%, 44px);
    transform: translate(-50%, -50%);
  }
  .bell[data-door="left"]  { left: 0.75%; }
  .bell[data-door="right"] { left: 90.65%; }
  .bell:hover:not(:disabled), .bell:focus-visible {
    box-shadow: 0 0 0 3px var(--brass), 0 0 22px 6px var(--brass-glow);
    outline: none;
  }
  .bell:disabled { cursor: not-allowed; }
  .bell.is-rung { box-shadow: 0 0 0 3px var(--brass), 0 0 26px 8px var(--brass-glow); }
  .bell .ring {
    position: absolute;
    left: 50%; top: 58%;
    width: 70%; aspect-ratio: 1;
    border-radius: 50%;
    border: 3px solid var(--brass);
    transform: translate(-50%, -50%) scale(0.6);
    opacity: 0;
    pointer-events: none;
  }
  .bell.is-ringing .ring { animation: ring 0.9s ease-out 3; }
  .bell.is-ringing .ring + .ring { animation-delay: 0.3s; }
  @keyframes ring {
    0%   { transform: translate(-50%, -50%) scale(0.6); opacity: 0.95; }
    100% { transform: translate(-50%, -50%) scale(2.1); opacity: 0; }
  }

  /* THE TAUNT: a speech bubble from the Knight who says Ni (head at about x 49 %, y 14 %). */
  .taunt {
    position: absolute;
    z-index: 4;
    left: 56%;
    top: 5%;
    transform: none;
    transform-origin: 0% 100%;
    max-width: min(40%, 460px);
    background: var(--parchment);
    border: 2px solid var(--iron);
    border-radius: 14px;
    padding: 0.35em 0.8em;
    font-family: var(--font-display);
    font-size: calc(clamp(15px, 2.2vw, 26px) * var(--display-scale));
    line-height: 1.15;
    text-align: center;
    color: var(--ink);
    box-shadow: 3px 4px 0 var(--iron);
    pointer-events: none;
  }
  .taunt::after {          /* the bubble's tail, pointing at the knight's mouth */
    content: "";
    position: absolute;
    left: -14px; bottom: 6px;
    width: 18px; height: 18px;
    background: var(--parchment);
    border-left: 2px solid var(--iron);
    border-bottom: 2px solid var(--iron);
    transform: skewY(-30deg) rotate(10deg);
  }
  .taunt[hidden] { display: none; }
  .taunt.is-shout { animation: shout 0.4s cubic-bezier(.2, 1.6, .4, 1); }
  @keyframes shout {
    from { transform: scale(0.5) rotate(-6deg); opacity: 0; }
    to   { transform: scale(1) rotate(0); opacity: 1; }
  }

  /* THE FOOT, after Bronzino's Cupid (1545) by way of Gilliam: down, stamp, up. */
  .foot {
    position: absolute;
    z-index: 5;
    left: 31%;
    bottom: -1%;
    width: 38%;
    pointer-events: none;
    transform: translateY(-140%);
  }
  .foot[hidden] { display: none; }
  .foot.is-stamping { animation: stamp 1.7s cubic-bezier(.5, 0, .75, 0) forwards; }
  @keyframes stamp {
    0%   { transform: translateY(-140%); }
    30%  { transform: translateY(0); animation-timing-function: linear; }
    62%  { transform: translateY(0); animation-timing-function: cubic-bezier(.3, 0, .2, 1); }
    100% { transform: translateY(-140%); }
  }
  .beast.is-squashed .body {
    transform-origin: 50% 100%;
    transform: scaleY(0.25) !important;
    transition: transform 0.12s ease-in;
  }

  /* THE LIVESTOCK: paper cut-outs. Each beast flies in a parabola: the outer box
     travels across, the inner one up and down, the body tumbles and lands upright. */
  .beast { position: absolute; z-index: 2; left: 0; top: 0; pointer-events: none; }
  .beast .arc { display: block; }
  .beast .body { display: block; transform-origin: 50% 60%; }
  .beast .body svg { display: block; width: 100%; height: auto; overflow: visible; }
  .beast.is-flying {
    animation: across var(--t, 1.4s) linear forwards;
  }
  .beast.is-flying .arc {
    animation: up-and-down var(--t, 1.4s) cubic-bezier(.3, 0, .7, 1) forwards;
  }
  .beast.is-flying .body {
    animation: tumble var(--t, 1.4s) linear forwards;
  }
  @keyframes across {
    from { transform: translateX(var(--x0)); }
    to   { transform: translateX(var(--x1)); }
  }
  @keyframes up-and-down {
    0%   { transform: translateY(var(--y0)); animation-timing-function: cubic-bezier(.2, .7, .4, 1); }
    45%  { transform: translateY(var(--ypeak)); animation-timing-function: cubic-bezier(.6, 0, .9, .5); }
    100% { transform: translateY(var(--y1)); }
  }
  @keyframes tumble {
    from { transform: rotate(0deg); }
    to   { transform: rotate(var(--spin, 540deg)); }
  }

  /* THE COWS fall from the top, upside down, and lie on their backs, legs
     kicking, as in the castle scene. The flip is its own box, so the foot can
     still squash a cow flat. */
  .beast .flip { display: block; transform: rotate(180deg); }
  .beast.is-dropping { animation: across var(--t, 1s) linear forwards; }
  .beast.is-dropping .arc { animation: fall var(--t, 1s) cubic-bezier(.5, 0, .9, .55) forwards; }
  .beast.is-dropping .body { animation: sway var(--t, 1s) ease-in-out forwards; }
  .beast.is-landed .body { animation: kick 0.42s ease-in-out infinite alternate; }
  @keyframes fall {
    from { transform: translateY(var(--y0)); }
    to   { transform: translateY(var(--y1)); }
  }
  @keyframes sway {
    0%   { transform: rotate(-16deg); }
    55%  { transform: rotate(12deg); }
    100% { transform: rotate(0deg); }
  }
  @keyframes kick {
    from { transform: rotate(-3deg); }
    to   { transform: rotate(3deg); }
  }

  /* The doors' names, under each door (door centres at 23.0 % and 76.9 %). */
  .names {
    display: grid;
    grid-template-columns: 9.8fr 26.4fr 27.4fr 26.5fr 9.9fr;
    margin-top: -6px;
  }
  .names span {
    font-family: var(--font-display);
    font-size: calc(clamp(14px, 1.8vw, 22px) * var(--display-scale));
    letter-spacing: var(--names-spacing);
    text-transform: var(--names-case);
    text-align: center;
    color: var(--iron);
  }
  .names .n-left { grid-column: 2; }
  .names .n-right { grid-column: 4; }

  /* What the visitor is told. */
  .status {
    justify-self: center;
    width: min(100%, 680px);
    text-align: center;
    display: grid;
    gap: 4px;
    padding-block: 6px;
  }
  .status[hidden] { display: none; }
  .bell[aria-busy="true"] { cursor: progress; }
  .status h2 {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: calc(clamp(21px, 2.7vw, 30px) * var(--display-scale));
    letter-spacing: 0.01em;
    margin: 0;
    text-wrap: balance;
  }
  .status p { margin: 0; color: var(--ink-soft); text-wrap: balance; }
  /* The address the door opens for: the API's view of this visitor. */
  .status .where { font-size: 16px; }
  .status .where[hidden] { display: none; }
  .where .addr {
    display: block;
    margin-top: 2px;
    font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
    font-size: clamp(12px, 3.7vw, 15px);   /* a full IPv6 address fits one line at 360 */
    color: var(--ink);
    overflow-wrap: anywhere;
  }
  .status.is-error h2 { color: #8c1d18; }
  .credit {
    margin: 4px 0 0;
    text-align: center;
    font-style: italic;
    font-size: 15px;
    color: var(--ink-soft);
  }

  @media (prefers-reduced-motion: reduce) {
    .bell.is-ringing .ring, .scene.is-thud, .taunt.is-shout { animation: none; }
    .beast .body { animation: none !important; }
    .foot { display: none; }
  }
