/* The Dragon's Audit: the art layer, drawn by art.js (ui.md, "Art"). Pixel art shown at whole multiples of its own
   size with image-rendering: pixelated, so every pixel stays square. Every box keeps its size whatever it shows,
   so nothing moves the text. Every animation is a short flourish, or a slow loop in a picture away from the
   text; under reduced motion each one stops on a still frame. With images blocked the boxes stay, empty. */

/* 1. The scene over the story. art/scenes.png holds a row of four 192 by 64 frames per scene, in the order of
   SCENES in art.js. Drawn at 3x, at 2x on a phone or a short screen; a column narrower than the picture crops its
   two sides. A new scene loops its frames three times, fewer where three would pass five seconds (--loops), and
   rests on frame one: nothing beside the text moves for more than five seconds (WCAG 2.2.2, UX review of
   30 Sep 2026). art.js starts the loop again when the scene changes. */

.scene-view {
  --u: 3px;
  position: relative;
  overflow: hidden;
  width: min(100%, calc(192 * var(--u) + 4px));
  height: calc(64 * var(--u) + 4px);
  margin: 0 0 20px;
  border: 2px solid var(--ink);
  background: var(--blue);
  box-shadow: 8px 8px 0 rgba(20, 20, 19, .14);
}

.scene-view i {
  position: absolute;
  top: 0;
  left: 50%;
  width: calc(192 * var(--u));
  height: calc(64 * var(--u));
  margin-left: calc(-96 * var(--u));
  background: url(./art/scenes.png) 0 calc(var(--row, 0) * -64 * var(--u)) / calc(768 * var(--u)) calc(1152 * var(--u)) no-repeat;
  image-rendering: pixelated;
}

.scene-view.loop i { animation: scene-loop var(--loop, 2s) steps(1, end) var(--loops, 3); }

/* the sheet is fetched with the page, so the first scene does not wait for it */
.scene-view img { display: none; }

.scene-view:not([data-scene]) i { visibility: hidden; }

/* the scene over the story says where the run is, so the small icon beside the act and scene steps aside */
.scene-view[data-scene] + .scene-head .scene-art { display: none; }

@keyframes scene-loop {
  0% { background-position-x: 0; }
  25% { background-position-x: calc(-192 * var(--u)); }
  50% { background-position-x: calc(-384 * var(--u)); }
  75% { background-position-x: calc(-576 * var(--u)); }
}

/* each scene: its row, how long its four frames take, and how many times they run when the scene comes up, at most
   five seconds in all. A candle flickers faster than smoke drifts. */
.scene-view[data-scene="office"] { --row: 0; --loop: 1.2s; }
.scene-view[data-scene="road"] { --row: 1; --loop: 2.4s; --loops: 2; }
.scene-view[data-scene="town"] { --row: 2; --loop: 2.4s; --loops: 2; }
.scene-view[data-scene="inn"] { --row: 3; --loop: 1.2s; }
.scene-view[data-scene="market"] { --row: 4; --loop: 1.6s; }
.scene-view[data-scene="bridge"] { --row: 5; --loop: 1.6s; }
.scene-view[data-scene="track"] { --row: 6; --loop: 2.4s; --loops: 2; }
.scene-view[data-scene="gate"] { --row: 7; --loop: 2.4s; --loops: 2; }
.scene-view[data-scene="concourse"] { --row: 8; --loop: 4s; --loops: 1; }
.scene-view[data-scene="hall"] { --row: 9; --loop: 1.2s; }
.scene-view[data-scene="canteen"] { --row: 10; --loop: 2s; --loops: 2; }
.scene-view[data-scene="archive"] { --row: 11; --loop: 1.2s; }
.scene-view[data-scene="warrens"] { --row: 12; --loop: 2s; --loops: 2; }
.scene-view[data-scene="vault"] { --row: 13; --loop: 1.2s; }
.scene-view[data-scene="antechamber"] { --row: 14; --loop: 1.2s; }
.scene-view[data-scene="hoard"] { --row: 15; --loop: 1.6s; }
.scene-view[data-scene="dragon"] { --row: 16; --loop: 2.4s; --loops: 2; }
.scene-view[data-scene="ending"] { --row: 17; --loop: 1.2s; }

/* 1b. The stage. Where a scene has people in it (stage.json, ui.md "The stage"), stage.js paints the place and the
   people on a canvas of the same 192 by 64 pixels over this box, and the loop above steps aside. The canvas is the
   box's own size at every size, so nothing moves the text, it takes no clicks, and while it is not there (stage.js
   not run, the file or an image blocked, a place without a stage) the picture above shows as it always did. */

.scene-view canvas.stage {
  position: absolute;
  top: 0;
  left: 50%;
  width: calc(192 * var(--u));
  height: calc(64 * var(--u));
  margin-left: calc(-96 * var(--u));
  image-rendering: pixelated;
  pointer-events: none;
  visibility: hidden;
}

.scene-view.staged[data-scene] canvas.stage { visibility: visible; }

.scene-view.staged i { visibility: hidden; }

/* 2. Icons. art/icons.png is one row of 16 by 16 icons in the order of ICONS in art.js. An entry in the file takes
   its icon in place of the square before it; a check takes the icon of each held thing that adds to it. */

[data-icon="letter"] { --icon: 0; }
[data-icon="red_pen"] { --icon: 1; }
[data-icon="abacus"] { --icon: 2; }
[data-icon="working_papers"] { --icon: 3; }
[data-icon="toll_receipt"] { --icon: 4; }
[data-icon="honey_cake"] { --icon: 5; }
[data-icon="lantern"] { --icon: 6; }
[data-icon="kobold_token"] { --icon: 7; }
[data-icon="dragon_scale"] { --icon: 8; }
[data-icon="ev_toll_ledger"] { --icon: 9; }
[data-icon="ev_mirror_seal"] { --icon: 10; }
[data-icon="ev_lurk_orders"] { --icon: 11; }
[data-icon="ev_stamped_coin"] { --icon: 12; }
[data-icon="ev_round_totals"] { --icon: 13; }
[data-icon="ev_fund_vault"] { --icon: 14; }
[data-icon="ev_winter_gift"] { --icon: 15; }
[data-icon="ev_night_flights"] { --icon: 16; }
[data-icon="ev_village_list"] { --icon: 17; }
[data-icon="ev_thrup_papers"] { --icon: 18; }
[data-icon="ev_count"] { --icon: 19; }
[data-icon="ev_confession"] { --icon: 20; }

/* :is() with the ids outranks the persimmon square game.css gives the evidence, and in the file sheet, where the
   copy has no ids, the plain list takes the same rule */
.file-body :is(#evidence, #items, ul) li[data-icon]::before {
  width: 16px;
  height: 16px;
  margin-top: 2px;
  background: url(./art/icons.png) calc(var(--icon, 0) * -16px) 0 / 336px 16px no-repeat;
  image-rendering: pixelated;
}

/* a new piece drops into the file */
.file-body li.fresh[data-icon]::before { animation: icon-drop .3s steps(1, end) both; }

@keyframes icon-drop {
  0% { opacity: 0; transform: translateY(-8px); }
  34% { opacity: 1; transform: translateY(-3px); }
  67% { transform: translateY(1px); }
  100% { transform: none; }
}

/* in a check tag: 16px high inside an 11px line, so the tag keeps its height */
.tag-icon {
  display: inline-block;
  width: 16px;
  height: 16px;
  margin: -3px -3px -2px 7px;
  vertical-align: top;
  background: url(./art/icons.png) calc(var(--icon, 0) * -16px) 0 / 336px 16px no-repeat;
  image-rendering: pixelated;
}

.tag-icon + .tag-icon { margin-left: 4px; }

/* the tab of the file glints once when something new goes in, and on a small screen the File button */
.file > .pane-bar {
  position: relative;
  overflow: hidden;
}

.file > .pane-bar::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 14px;
  background: var(--paper);
  opacity: 0;
  pointer-events: none;
}

.file > .pane-bar.glint::after { animation: tab-glint .5s steps(10, end) both; }
.file > .pane-bar.glint i { animation: tab-light .5s steps(1, end); }
#open-file.glint { animation: file-button .6s steps(1, end); }

@keyframes tab-glint {
  0% { opacity: .85; transform: translateX(-24px) skewX(-24deg); }
  99% { opacity: .85; transform: translateX(360px) skewX(-24deg); }
  100% { opacity: 0; transform: translateX(360px) skewX(-24deg); }
}

@keyframes tab-light {
  0%, 60% { background: var(--hot); border-color: var(--hot); }
}

@keyframes file-button {
  0%, 50% { background: var(--hot); color: var(--ink); }
}

/* 4. The stat of a check. art/stats.png: nerve, wit, charm left to right, unlit above and lit below. On the roll
   card the glyph turns on as the dice tumble and stays lit; in the file it lights until the next move. */

[data-stat="nerve"] { --stat: 0; }
[data-stat="wit"] { --stat: 1; }
[data-stat="charm"] { --stat: 2; }

.stat-glyph {
  display: none;
  flex: none;
  width: 32px;
  height: 32px;
  margin: 0 -4px;
  background: url(./art/stats.png) calc(var(--stat, 0) * -32px) -32px / 96px 64px no-repeat;
  image-rendering: pixelated;
}

.rollcard[data-stat] .stat-glyph { display: block; }

.rollcard.rolling .stat-glyph { animation: glyph-on .76s steps(1, end) both; }

@keyframes glyph-on {
  0% { background-position-y: 0; }
  40% { background-position-y: -32px; }
  52% { background-position-y: 0; }
  64%, 100% { background-position-y: -32px; }
}

.figures [data-stat] .label::before {
  content: "";
  display: inline-block;
  width: 16px;
  height: 16px;
  margin: -3px 5px -2px 0;
  vertical-align: top;
  background: url(./art/stats.png) calc(var(--stat, 0) * -16px) 0 / 48px 32px no-repeat;
  image-rendering: pixelated;
}

.figures[data-lit="nerve"] [data-stat="nerve"] .label::before,
.figures[data-lit="wit"] [data-stat="wit"] .label::before,
.figures[data-lit="charm"] [data-stat="charm"] .label::before {
  background-position-y: -16px;
  animation: glyph-flash .5s steps(1, end);
}

@keyframes glyph-flash {
  0%, 40% { background-position-y: 0; }
}

/* 5. The ending card. art/endings.png holds five 192 by 96 cards, in the order of CARDS in art.js, drawn like the
   scene and wiped in from the left when the ending is reached. */

.ending-card {
  --u: 3px;
  position: relative;
  overflow: hidden;
  width: min(100%, calc(192 * var(--u) + 4px));
  height: calc(96 * var(--u) + 4px);
  margin: 0 0 30px;
  border: 2px solid var(--ink);
  background: var(--blue);
  box-shadow: 8px 8px 0 rgba(20, 20, 19, .14);
}

.ending-card i {
  position: absolute;
  top: 0;
  left: 50%;
  width: calc(192 * var(--u));
  height: calc(96 * var(--u));
  margin-left: calc(-96 * var(--u));
  background: url(./art/endings.png) calc(var(--card, 0) * -192 * var(--u)) 0 / calc(960 * var(--u)) calc(96 * var(--u)) no-repeat;
  image-rendering: pixelated;
}

.ending-card[data-card="clean"] { --card: 0; }
.ending-card[data-card="qualified"] { --card: 1; }
.ending-card[data-card="adverse"] { --card: 2; }
.ending-card[data-card="disclaimer"] { --card: 3; }
.ending-card[data-card="custodian"] { --card: 4; }

.ending-card.reveal i { animation: card-wipe .48s steps(8, end) both; }

@keyframes card-wipe {
  from { clip-path: inset(0 100% 0 0); }
  to { clip-path: inset(0); }
}

/* 6. The red thread down the left edge of the play screen: the time to year end, full at five days, with a knot
   at its end. It pulses once when a day goes. Fixed, so it takes no room from the page. */

.thread {
  position: fixed;
  z-index: 4;
  top: 0;
  left: max(10px, calc((100vw - 1296px) / 2 - 34px));
  display: none;
  width: 2px;
  height: 100vh;
  pointer-events: none;
}

body:has(#screen-play:not([hidden])) .thread { display: block; }

.thread i {
  position: relative;
  display: block;
  height: calc(var(--t, 10) * 10%);
  background: var(--hot);
  transition: height .5s steps(5, end);
}

.thread i::after {
  content: "";
  position: absolute;
  left: -3px;
  bottom: -4px;
  width: 8px;
  height: 8px;
  background: var(--hot);
}

.thread[data-time="0"] { --t: 0; }
.thread[data-time="1"] { --t: 1; }
.thread[data-time="2"] { --t: 2; }
.thread[data-time="3"] { --t: 3; }
.thread[data-time="4"] { --t: 4; }
.thread[data-time="5"] { --t: 5; }
.thread[data-time="6"] { --t: 6; }
.thread[data-time="7"] { --t: 7; }
.thread[data-time="8"] { --t: 8; }
.thread[data-time="9"] { --t: 9; }
.thread[data-time="10"] { --t: 10; }

.thread.pulse i { animation: thread-pulse .72s steps(1, end); }
.thread.pulse i::after { animation: knot-pulse .72s steps(1, end); }

@keyframes thread-pulse {
  0%, 56% { box-shadow: 0 0 0 2px var(--hot); }
}

@keyframes knot-pulse {
  0%, 56% { box-shadow: 0 0 0 3px var(--paper), 0 0 0 5px var(--hot); }
}

/* 3. The title. The dragon's eye, art/eye.png: open, a third shut, two thirds shut, shut, and looking down at the
   buttons. It opens as the page loads, blinks when art.js says, three times at most, and looks at a button the
   mouse is on, which gives off a spark from art/spark.png. */

.marquee-art {
  display: block;
  aspect-ratio: 44 / 26;
  background: url(./art/eye.png) 0 0 / 500% 100% no-repeat;
  image-rendering: pixelated;
  animation: eye-open .54s steps(1, end) .3s backwards;
}

.marquee-art.blink { animation: eye-blink .42s steps(1, end); }

@keyframes eye-open {
  0% { background-position-x: 75%; }
  33% { background-position-x: 50%; }
  67% { background-position-x: 25%; }
}

@keyframes eye-blink {
  0% { background-position-x: 25%; }
  17% { background-position-x: 50%; }
  33% { background-position-x: 75%; }
  58% { background-position-x: 50%; }
  75% { background-position-x: 25%; }
  92% { background-position-x: 0; }
}

@media (hover: hover) and (pointer: fine) {
  .marquee:has(.marquee-actions .btn:hover) .marquee-art { background-position-x: 100%; }

  .marquee-actions .btn { position: relative; }

  .marquee-actions .btn::before,
  .marquee-actions .btn::after {
    content: "";
    position: absolute;
    width: 27px;
    height: 27px;
    background: url(./art/spark.png) 0 0 / 81px 27px no-repeat;
    image-rendering: pixelated;
    opacity: 0;
    pointer-events: none;
  }

  .marquee-actions .btn::before { left: -11px; bottom: -12px; }
  .marquee-actions .btn::after { right: -11px; top: -12px; }

  .marquee-actions .btn:hover::after { animation: spark .42s steps(1, end) both; }
  .marquee-actions .btn:hover::before { animation: spark .42s steps(1, end) .12s both; }
}

@keyframes spark {
  0% { opacity: 1; background-position-x: 0; }
  33% { opacity: 1; background-position-x: -27px; }
  67% { opacity: 1; background-position-x: -54px; }
  100% { opacity: 0; background-position-x: -54px; }
}

/* phones: the pictures at 2x, the thread nearer the edge */

@media (max-width: 680px) {
  .scene-view,
  .ending-card { --u: 2px; }

  .thread { left: 7px; }
}

/* short screens, 900px tall or less, such as a 1366 by 768, a 1280 by 800 or a 1440 by 900 laptop: the scene at 2x
   too, with a smaller gap under it, so the first choice is on screen after a move and after Continue (UX review of
   30 Sep 2026; QA, 1 Oct 2026) */

@media (max-height: 900px) {
  .scene-view {
    --u: 2px;
    margin-bottom: 12px;
  }
}

/* the size art.js chose for the node on screen (fit(), ui.md "Art"): the largest of these that keeps the first choice
   whole. "3" and "2" are the whole picture; "mid" and "low" crop it to the band of 48 and of 32 rows around the
   middle, at 2x, so every phone shows a picture and the longest nodes scroll (Mike, 1 Oct 2026: "i cannot see any
   graphic"; before it the picture was hidden under 480px wide for the sake of the fold). */

.scene-view[data-fit="3"] { --u: 3px; margin-bottom: 20px; }

.scene-view[data-fit="2"],
.scene-view[data-fit="mid"],
.scene-view[data-fit="low"] {
  --u: 2px;
  margin-bottom: 12px;
}

.scene-view[data-fit="mid"] { height: calc(48 * var(--u) + 4px); }
.scene-view[data-fit="mid"] i { top: calc(-8 * var(--u)); }

.scene-view[data-fit="low"] { height: calc(32 * var(--u) + 4px); }
.scene-view[data-fit="low"] i { top: calc(-16 * var(--u)); }

/* the bands of a stage are the bands of the picture, so the cast stands inside them (the Game Art draft of act 1: feet
   inside rows 8 to 55 and 16 to 47, the three high scenes as below) ... */
.scene-view.staged[data-fit="mid"] canvas.stage { top: calc(-8 * var(--u)); }
.scene-view.staged[data-fit="low"] canvas.stage { top: calc(-16 * var(--u)); }

/* ... but the office, where the cast stands on row 61, takes the foot of the picture: "mid" is rows 16 to 63 and "low"
   rows 24 to 63, the strip, 80px high and 84 with the border (the Game Art plan of 1 Oct 2026) */
.scene-view.staged[data-scene="office"][data-fit="mid"] canvas.stage { top: calc(-16 * var(--u)); }

.scene-view.staged[data-scene="office"][data-fit="low"] { height: calc(40 * var(--u) + 4px); }
.scene-view.staged[data-scene="office"][data-fit="low"] canvas.stage { top: calc(-24 * var(--u)); }

/* the three scenes whose subject sits high, so the low band starts nearer the top row (the Game Art seat, 1 Oct 2026:
   Grend on the bridge, the goat on the track, the eye in the hoard) */
.scene-view[data-scene="bridge"][data-fit="low"] i { top: calc(-4 * var(--u)); }
.scene-view[data-scene="track"][data-fit="low"] i { top: calc(-6 * var(--u)); }
.scene-view[data-scene="hoard"][data-fit="low"] i { top: calc(-2 * var(--u)); }

.scene-view.staged[data-scene="bridge"][data-fit="low"] canvas.stage { top: calc(-4 * var(--u)); }
.scene-view.staged[data-scene="track"][data-fit="low"] canvas.stage { top: calc(-6 * var(--u)); }
.scene-view.staged[data-scene="hoard"][data-fit="low"] canvas.stage { top: calc(-2 * var(--u)); }

@media (prefers-reduced-motion: reduce) {
  .scene-view.loop i,
  .file-body li.fresh[data-icon]::before,
  .file > .pane-bar.glint::after,
  .file > .pane-bar.glint i,
  #open-file.glint,
  .rollcard.rolling .stat-glyph,
  .figures[data-lit] .label::before,
  .ending-card.reveal i,
  .thread.pulse i,
  .thread.pulse i::after,
  .marquee-art,
  .marquee-art.blink,
  .marquee-actions .btn:hover::before,
  .marquee-actions .btn:hover::after { animation: none; }

  .thread i { transition: none; }
}
