/* ===============================================================
   Beyond The Sidelines — Delegate
   Han Tran Dao Mai · Mái Chéo · COMM2754 · RMIT University Vietnam
   Copyright (C) 2026 Han Tran Dao Mai — GNU GPL v3, see the LICENSE file.
   =============================================================== */

/* ---------------------------------------------------------------
   Nở — a 1920×1080 frame
     · the leaf image sits in the middle of the frame
     · a see-through 3D canvas on top, with flowers growing at the stem tips

   --art-w and --art-nudge decide where the image sits. The JS reads those same
   two numbers to place the flowers to match, so change them here and the
   flowers follow.
   --------------------------------------------------------------- */

:root {
  --scale: 1;
  --bleed-x: 0px;       /* JS overwrites this: width of ONE empty strip from the 16:9 lock */
  --bleed-y: 0px;       /* same, the strips above/below when the window is taller than 16:9 */
  --art-w: 1620px;      /* how wide the image is inside the frame */
  --art-nudge: -60px;   /* negative = the image sits lower than the frame centre */
}

/* Both fonts sit in assets — no network call, so it still works offline */
@font-face {
  font-family: 'HelveticaLocal';
  src: url('assets/COMM2754-2026-S2-A3w12-BeyondTheSidelines-Helvetica-font.ttf') format('truetype');
  font-weight: 400;
  font-display: block;
}
@font-face {
  font-family: 'MeaCulpa';
  src: url('assets/COMM2754-2026-S2-A3w12-BeyondTheSidelines-MeaCulpa-Regular-font.ttf') format('truetype');
  font-weight: 400;
  font-display: block;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html, body {
  height: 100%;
  /* The frame is locked to 16:9, so a window with a different ratio leaves two
     empty strips. The page background uses the exact same colour as the frame,
     so you never see a seam. */
  background: #2A0009;
  overflow: hidden;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
}

/* The frame is pinned to the centre of the window. Not centred with flex or
   grid, because the 1920px frame is usually bigger than the window and those
   would shove it off to one edge. */
#frame {
  position: fixed;
  left: 50%;
  top: 50%;
  width: 1920px;
  height: 1080px;
  /* Open on both axes: the text runs out to the window edge, and the canvas
     stretches out on all four sides so the background covers the empty strips.
     Without that the frame edge shows up as a visible line. */
  overflow: visible;
  background: #2A0009;
  /* The fallback matters now that the script is a separate module: for the one
     paint before it runs, --scale is not set yet. Without a fallback the whole
     declaration would be invalid and the frame would not even be centred. At
     1920×1080 — what this is made for — the fallback is the right number. */
  transform: translate(-50%, -50%) scale(var(--scale, 1));
  transform-origin: center center;
}

/* The leaf image sits under the canvas and you only see it for a moment before
   the 3D layer is built. In the 3D scene the whole plant is scaled to 80%, so
   this has to be multiplied by 0.8 to match — otherwise you see the leaves jump
   smaller the moment 3D takes over. */
#subject {
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc(var(--art-w) * 0.8);
  height: auto;
  transform: translate(-50%, calc(-50% - var(--art-nudge) * 0.8));
}

/* The canvas sits on top of the image with a transparent background, stretched
   out over the empty strips on all four sides. The camera's view is widened by
   exactly that much, so nothing is scaled — you just see more. That way the
   background colour runs all the way to the window edge. */
#scene {
  position: absolute;
  /* A canvas doesn't stretch with inset the way a div does, so set width/height. */
  left: calc(-1 * var(--bleed-x));
  top:  calc(-1 * var(--bleed-y));
  width:  calc(1920px + 2 * var(--bleed-x));
  height: calc(1080px + 2 * var(--bleed-y));
  display: block;
}

/* The film grain covers the frame only. Stretching it over the empty strips
   breaks it: mix-blend-mode needs something to blend with, and outside the frame
   there is nothing, so the grain prints straight on and the strips turn brighter
   — which reads as a border. Inside the frame the canvas is there to blend with. */
#grain {
  position: absolute;
  inset: 0;
  pointer-events: none;
  mix-blend-mode: overlay;
  opacity: .055;
  background-repeat: repeat;
  animation: grainshift .8s steps(3) infinite;
}

@keyframes grainshift {
  0%   { background-position:   0    0; }
  33%  { background-position: -41px 23px; }
  66%  { background-position:  27px -35px; }
  100% { background-position:   0    0; }
}


/* ---------------------------------------------------------------
   The text layer

   h1, h2 and h3 all share one size (--type-size). The handwritten W looks
   bigger only because the script stroke rises high and has a tail, which is how
   the design wants it — no separate size needed.
   --------------------------------------------------------------- */

:root {
  --type-size: 132px;
  --ink: #FDFCFD;                          /* picked again on every load, see the colour picker below */
  --grid-pad: 30px;                        /* frame padding before it is split into 12 cells */
  --mark-size: 39px;                       /* diameter of the ring around the i */
  --mark-ink: #CBE45C;                     /* yellow-green: the ring and the i are drawn in it */
  --optical-r: -.042em;                    /* cancels the extra space on the right of the text */
  --optical-l: -.055em;                    /* cancels the built-in space to the left of I and N */
  --grid-line: rgba(253, 242, 242, .28);   /* colour of the guide grid you get with the G key */
}

#type {
  position: absolute;
  /* top/bottom hug the frame; left/right stretch over the empty strips to the window edge */
  inset: 0 calc(-1 * var(--bleed-x));
  pointer-events: none;
  color: var(--ink);
  /* Three layers of text shadow: tight, mid, then a wide halo. Same light
     direction as the flower and leaf shadows (light from the top right, shadow
     falling to the bottom left), so the whole frame reads as one thing with
     depth instead of text pasted flat onto a picture. */
  text-shadow:
    0 1px 2px rgba(42, 0, 9, .62),
    -1px 4px 10px rgba(20, 0, 6, .5),
    -2px 10px 26px rgba(20, 0, 6, .42);

  /* A 12×12 grid across the frame, minus --grid-pad on each side. A block of
     text only has to say which cells it takes; no odd pixel numbers anywhere. */
  display: grid;
  padding: var(--grid-pad);
  grid-template-columns: repeat(12, 1fr);
  grid-template-rows: repeat(12, 1fr);
}

#type h1, #type h2, #type h3 {
  font-size: var(--type-size);
  line-height: .94;
  font-weight: 400;
  /* At 132px the shadow has to fall much further than it would on small text,
     or it just looks like small text blown up rather than text standing off the
     background. */
  text-shadow:
    0 2px 3px rgba(42, 0, 9, .58),
    -2px 8px 18px rgba(20, 0, 6, .52),
    -5px 20px 48px rgba(20, 0, 6, .45),
    0 0 84px rgba(42, 0, 9, .35);
}

#type h1, #type h2, .blurb, .hint p {
  font-family: 'HelveticaLocal', Helvetica, Arial, sans-serif;
}

#type h1, #type h2 { letter-spacing: .035em; }

#type h3 {
  font-family: 'MeaCulpa', cursive;
  /* pull the W so it just overlaps the H, like the design — but only touching;
     any deeper and the two letters read as one blob */
  margin-right: 35px;
  line-height: 1;
}

/* --- three corners for three blocks of text ---
   The positions are not hard-coded: the JS at the end of the page shuffles the
   corners and hands them out, so every load is a different layout. All this does
   is say what tl/bl/br mean in grid cells. Top right is missing on purpose — it
   is reserved for the group logo.

   Columns: left = 1–6, right = 7–12.  Rows: top = 1–6, bottom = 7–12. Each block
   gets half the grid, so even the tallest one (IF NOT / NOW, / WHEN?) fits.  */

.at-tl, .at-bl { grid-column: 1 / 7;  justify-self: start; text-align: left;  }
.at-br         { grid-column: 7 / -1; justify-self: end;   text-align: right; }
.at-tl         { grid-row: 1 / 7;     align-self: start; }
.at-bl, .at-br { grid-row: 7 / -1;    align-self: end;   }

/* Line things up by the actual ink, not by the edge of the text box. Every
   letter carries some blank space on each side, and letter-spacing adds another
   gap after the last character. These negative margins pull the real ink out to
   the edge, on whichever side the block is pushed against. */
.at-tl h1, .at-bl h1 { margin-left: var(--optical-l); }
.at-tl h3, .at-bl h3 { margin-left: calc(var(--optical-l) * .4); }
.at-br h1, .at-br h2 { margin-right: var(--optical-r); }

/* A block grows from the corner it is pinned to, never from its middle — the
   script scales whichever block the pointer is over, and growing from the centre
   would push the ink off the edge of the frame. */
.at-tl { transform-origin: left top;    }
.at-bl { transform-origin: left bottom; }
.at-br { transform-origin: right bottom;}

.word { display: flex; align-items: baseline; }
.at-br .word { justify-content: flex-end; }

.blurb {
  max-width: 620px;      /* half the grid is 960px wide; left free the lines run too long */
  font-size: 26px;
  line-height: 1.5;
  letter-spacing: .01em;
}

/* --- top right: the group logo + the how-to-play text ---
   This sits in one fixed spot and is left out of the shuffle: it is the way out
   of the artwork, so it has to be in the same place no matter how many times you
   reload. That is why the shuffle only has three corners for three blocks of
   text, and why text can never land on top of the logo.

   The right edge subtracts --bleed-x the same way the text layer does, so this
   and the right edge of the text line up however wide the window gets. */
#corner-tr {
  position: absolute;
  top: var(--grid-pad);
  right: calc(var(--grid-pad) - var(--bleed-x));
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: flex-end;      /* every mark and line shares one right edge */
  gap: 26px;                  /* breathing room between the marks and the text */
  pointer-events: none;       /* let the mouse pass through so you can still drag; the link turns it back on */
}

/* The logo and the sound switch share the top of the corner. The gap is wide
   enough that the two read as two separate things — sitting them close made the
   logo look like the button's own icon. */
.corner-top {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 30px;
}

/* No disc and no icon — it wears the same type as the logo's own label, in the
   same ink as every other word in the frame, so it belongs to the picture rather
   than sitting on top of it. Brightness alone says whether the sound is on. */
#soundBtn {
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  pointer-events: auto;
  font-family: 'HelveticaLocal', Helvetica, Arial, sans-serif;
  font-size: 20px;
  letter-spacing: .16em;
  color: var(--ink);
  text-shadow:
    0 1px 2px rgba(42, 0, 9, .62),
    -1px 4px 10px rgba(20, 0, 6, .5);
  opacity: .82;
  transition: opacity .3s ease;
  /* Centred under the disc rather than under the whole logo group. The group is
     right-aligned and the disc sits at its right end, so pulling in by half a
     disc and then pushing back by half the word puts the two centres in line,
     whatever the word measures. */
  margin-right: calc(var(--mark-size) / 2);
  transform: translateX(50%);
}

#soundBtn.off { opacity: .3; }

#soundBtn:hover { opacity: 1; }

/* A tab stop should still be visible, but in the frame's own ink rather than the
   browser's blue. */
#soundBtn:focus { outline: none; }
#soundBtn:focus-visible {
  opacity: 1;
  outline: 2px solid var(--ink);
  outline-offset: 7px;
  border-radius: 2px;
}

#infoBtn {
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-direction: row-reverse;   /* ring faces the edge, label reads inward */
  pointer-events: auto;
}

/* An open ring with an i inside, drawn in a yellow-green that nothing else in
   the frame uses. The artwork is a dark plum and every word in it is white or
   pink, so this one colour is enough to say "this is not part of the picture,
   this is the thing you press" — no solid disc needed underneath. */
#infoBtn .disc {
  width: var(--mark-size);
  height: var(--mark-size);
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: none;
  border: 1.5px solid var(--mark-ink);
  /* A soft halo in the ring's own colour, outside and in, so the shape glows off
     the plum instead of sitting flat on it. Both the stroke and the halo are cut
     back with the diameter — a 2.5px ring that read as a hairline at 78px would
     read as a thick band at half that. */
  box-shadow:
    0 0 10px rgba(203, 228, 92, .45),
    inset 0 0 7px rgba(203, 228, 92, .18);
  transition: transform .4s ease, background .4s ease, box-shadow .4s ease;
}

/* The i. Sized off the ring rather than typed in, so changing --mark-size alone
   keeps the letter in proportion. */
#infoBtn .glyph {
  font-family: 'HelveticaLocal', Helvetica, Arial, sans-serif;
  font-size: calc(var(--mark-size) * .42);
  line-height: 1;
  color: var(--mark-ink);
  text-shadow: 0 0 7px rgba(203, 228, 92, .55);
  transition: color .4s ease, text-shadow .4s ease;
  /* Helvetica hangs a lowercase i high in its line box; this drops the letter
     back onto the ring's own centre. */
  transform: translateY(.06em);
}

/* --- the bulb ---
   Same shape of thing as the i: a mark on the right, a label that only arrives
   under the pointer. No ring around it — the drawing is already a closed shape,
   and a second circle would make two marks that read as one.

   The rays are in the SVG from the start and simply held at nothing, so hover
   lights the lamp that is already there rather than swapping in a different
   picture. Unlit it is drawn in the frame's own --ink, like every other word
   around it; lit it takes the mark colour, which is the one colour in the frame
   that only the controls use. */
#bulbBtn {
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-direction: row-reverse;
  /* Nothing to explain before the story starts: the garden is just in bloom and
     there are no bugs, no faces and no main flower to point at. So the bulb
     arrives with the how-to-play line, on the same cue and in the same way, and
     goes again when the garden is put back. Opacity rather than display, so the
     text below it never jumps up into the gap. */
  opacity: 0;
  transform: translateY(10px);
  pointer-events: none;
  transition: opacity .75s ease, transform .75s ease;
}

#bulbBtn.ready {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

#bulbBtn .bulb {
  /* the drawing is 56 wide but the glass is only about 25 of that — the rest is
     room for the rays — so it is set larger than the ring to match it by eye */
  width: calc(var(--mark-size) * 1.35);
  height: calc(var(--mark-size) * 1.35);
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  color: var(--ink);
  opacity: .6;
  transition: color .4s ease, opacity .4s ease, transform .4s ease, filter .4s ease;
}

#bulbBtn .bulb svg { width: 100%; height: 100%; display: block; overflow: visible; }

/* Held at nothing and pulled in towards the glass, so switching on reads as the
   light travelling outwards instead of seven lines blinking into place. The
   origin is the middle of the drawing, which is where the glass sits. */
#bulbBtn .rays {
  opacity: 0;
  transform: scale(.55);
  transform-origin: 50% 43%;
  transition: opacity .35s ease, transform .45s cubic-bezier(.2, .8, .3, 1.2);
}

#bulbBtn:hover .bulb,
#bulbBtn:focus-visible .bulb,
#bulbBtn.on .bulb {
  color: var(--mark-ink);
  opacity: 1;
  transform: scale(1.06);
  /* the glow belongs to the ink of the drawing, so it follows the stroke rather
     than boxing the icon the way a box-shadow would */
  filter: drop-shadow(0 0 7px rgba(203, 228, 92, .65));
}

#bulbBtn:hover .rays,
#bulbBtn:focus-visible .rays,
#bulbBtn.on .rays { opacity: 1; transform: scale(1); }

#bulbBtn:focus-visible { outline: none; }
#bulbBtn:focus-visible .label { opacity: 1; transform: none; }

/* The label only shows on hover (or when you tab to it). The frame is already
   full of text, so one more permanent line would be too much. */
#infoBtn .label,
#bulbBtn .label {
  font-family: 'HelveticaLocal', Helvetica, Arial, sans-serif;
  font-size: 14px;
  letter-spacing: .16em;
  white-space: nowrap;
  color: var(--ink);
  text-shadow:
    0 1px 2px rgba(42, 0, 9, .62),
    -1px 4px 10px rgba(20, 0, 6, .5);
  opacity: 0;
  transform: translateX(10px);
  transition: opacity .35s ease, transform .35s ease;
}

/* On hover the ring fills in and the letter drops out of it in the plum of the
   artwork — the same two colours swapping places, so it reads as the same mark
   switched on rather than a different one. Open, it stays filled, which is how
   you can tell at a glance that the panel below belongs to it. */
#infoBtn:hover .disc,
#infoBtn:focus-visible .disc,
#infoBtn.on .disc {
  transform: scale(1.08);
  background: var(--mark-ink);
  box-shadow: 0 0 15px rgba(203, 228, 92, .6);
}
#infoBtn:hover .glyph,
#infoBtn:focus-visible .glyph,
#infoBtn.on .glyph { color: #3A0016; text-shadow: none; }

#infoBtn:hover .label,
#infoBtn:focus-visible .label,
#infoBtn.on .label,
#bulbBtn:hover .label,
#bulbBtn.on .label { opacity: 1; transform: none; }

#infoBtn:focus-visible { outline: none; }
#infoBtn:focus-visible .disc { box-shadow: 0 0 0 3px var(--ink); }

/* --- the written page, washed over the frame ---
   A pink wash rather than a dark one, and the artwork still showing through it:
   this is the piece talking about itself, so it should feel like a layer of the
   picture rather than a window cut out of it. It stretches over the empty strips
   the same way #type does, so the wash reaches the edge of the window however
   wide it gets, not just the edge of the 1920 frame. */
#about {
  position: absolute;
  inset: 0 calc(-1 * var(--bleed-x));
  z-index: 21;
  overflow-y: auto;
  /* Deeper again, and holding back more of the garden than it did. The pink is
     the same hue the whole piece is built on, just taken well down in tone: at
     this weight the writing is the thing you read and the garden behind it is
     only a texture, which is the right way round for a page of text. */
  background: rgba(118, 12, 69, .90);
  opacity: 0;
  transition: opacity .4s ease;
}

#about[hidden] { display: none; }
#about.on { opacity: 1; }

/* While the page is up, the frame's own controls go.

   The artwork is meant to show through the wash — that is the look. Its
   controls are not: a mark, a sound switch and a flower you can still see but
   can no longer press read as broken rather than as scenery. The writing has
   its own way out, and that is the only thing to press while it is open. */
body.reading #corner-tr,
body.reading #startBtn,
body.reading #startcue {
  opacity: 0;
  pointer-events: none;
  transition: opacity .3s ease;
}

.about__sheet {
  /* Every size in the writing below is written against this, so the page grows
     as one thing. The wordmark is deliberately not: it is set as a share of the
     frame rather than as type, so it scales with the window instead. */
  --about-scale: 1.2;
  position: relative;
  min-height: 100%;
  box-sizing: border-box;
  /* Tight enough that one D still lands on one screen at this type size. The
     pages in the set that carry all five are meant to scroll; this one carries
     one, so a page that scrolled by an inch would read as an accident. */
  padding: 56px 96px 60px;
  color: #FDFCFD;
  font-family: 'HelveticaLocal', Helvetica, Arial, sans-serif;
  /* comes up from under as it fades in, so it arrives rather than appears */
  transform: translateY(16px);
  transition: transform .45s cubic-bezier(.2, .8, .3, 1);
}

#about.on .about__sheet { transform: none; }

/* The wordmark, top left, at the size it carries the page from. It is the same
   green as the headings below it, so nothing here needs recolouring. */
.about__mark { margin: 0 0 38px; }

.about__mark img {
  /* The wordmark carries the page, so it is sized to be the first and loudest
     thing on it — 1152px, which is 60% of the 1920 frame. The other panels in
     the set open their title at 60vw, and this page's frame is fitted to the
     width of the window, so 60% of the frame lands at the same size on screen
     and all four read alike. Written in frame pixels, not vw, because
     everything in here is drawn in the frame's own coordinates and then scaled
     with it. The percentage is the fallback: on a window too narrow for the
     sheet it gives way first. */
  width: min(1152px, 100%);
  height: auto;
  display: block;
}

/* The way out, in the top right corner — the same place the x sat on the page
   this replaces. An open ring, in the white everything else here is written in,
   because on a field this pink the yellow-green of the marks in the frame has
   nothing to hold on to. */
.about__close {
  position: absolute;
  top: 46px; right: 90px;
  width: 54px; height: 54px;
  display: grid; place-items: center;
  padding: 0;
  border: 1.5px solid rgba(253, 252, 253, .75);
  border-radius: 50%;
  background: none;
  color: #FDFCFD;
  cursor: pointer;
  transition: background .3s ease, color .3s ease, transform .3s ease;
}
.about__close svg { width: 52%; height: 52%; }
.about__close:hover,
.about__close:focus-visible {
  background: #FDFCFD;
  color: #C22274;
  transform: scale(1.08);
  outline: none;
}

/* The writing stops well short of the right edge. Lines running the whole width
   of a 1920 frame are too long to read; this is roughly what the eye will take
   in one go. */
/* The measure grows with the type. Holding the width while the type gets
   bigger would quietly shorten every line; scaling both keeps roughly the same
   number of words to a line, which is what makes a measure readable. */
.about__body { max-width: calc(1180px * var(--about-scale)); }

/* Small, green, spaced — a field name, not a heading. */
.about__label {
  font-size: calc(19px * var(--about-scale));
  font-weight: 700;
  letter-spacing: .12em;
  color: #A9E88A;
  margin: 0 0 12px;
}

/* Named with its element as well, so it actually beats the .about__body p rule
   below it — on the class alone that rule is more specific and this size never
   reached the page. */
.about__body p.about__name {
  font-size: calc(22px * var(--about-scale));
  letter-spacing: .01em;
  margin: 0 0 54px;
}

/* The D itself, with the green rule under it that every page in the set uses. */
.about__d {
  font-size: calc(38px * var(--about-scale));
  font-weight: 700;
  letter-spacing: .04em;
  margin: 0 0 18px;
  padding-bottom: 22px;
  border-bottom: 2px solid #A9E88A;
}

.about__body p {
  font-size: calc(21px * var(--about-scale));
  line-height: 1.6;
  letter-spacing: .01em;
  margin: 0 0 28px;
}

/* Dots rather than the dashes the old corner panel used, to match the other
   pages in the set. The marker is drawn in rather than left to the browser, so
   it keeps the same colour and spacing as the text it belongs to. */
.about__steps { list-style: none; margin: 0 0 28px; padding: 0; }

.about__steps li {
  position: relative;
  padding-left: calc(26px * var(--about-scale));
  font-size: calc(21px * var(--about-scale));
  line-height: 1.6;
  margin-bottom: 10px;
}

.about__steps li::before {
  content: "\2022";
  position: absolute;
  left: 2px;
}

/* --- the how-to-play text and the message, right under the logo ---
   These sit outside #type, so they don't inherit that layer's colour and shadow
   and have to set their own — the same --ink the three blocks of text use, so
   every line in the frame is drawn in one colour and they all change together
   on a reload. */
.hint {
  max-width: 620px;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 12px;
  text-align: right;
  color: var(--ink);
  text-shadow:
    0 1px 2px rgba(42, 0, 9, .62),
    -1px 4px 10px rgba(20, 0, 6, .5),
    -2px 10px 26px rgba(20, 0, 6, .42);
  /* This tells you how to play, it isn't an opening line: before you press the
     button there is nothing to drag, so showing it early just gives things away.
     Hidden until the flower cycle starts — the JS adds and removes .on. It uses
     opacity rather than display so it still holds its place. */
  opacity: 0;
  transform: translateY(10px);
  transition: opacity .75s ease, transform .75s ease;
}

.hint.on {
  opacity: 1;
  transform: none;
}

.hint p {
  line-height: 1.5;
  letter-spacing: .01em;
}

/* How to play. It is the only thing telling you what to do with the frame, and
   at this size, in this corner, a still line goes unread — so it blinks. Small
   it stays: the blink is what carries it, not the size.

   Brightness and glow, never size or position, the same as every other pulse in
   the frame — the corner it lives in is right under the mark and the sound
   switch, and a line that changed size would shove both of them about. */
.hint .do {
  font-size: 17px;
  letter-spacing: .06em;
  opacity: .9;
  animation: hintBlink 1.7s ease-in-out infinite;
}

@keyframes hintBlink {
  0%, 100% {
    opacity: .42;
    text-shadow:
      0 1px 2px rgba(42, 0, 9, .62),
      -1px 4px 10px rgba(20, 0, 6, .5);
  }
  50% {
    opacity: 1;
    text-shadow:
      0 1px 2px rgba(42, 0, 9, .62),
      -1px 4px 10px rgba(20, 0, 6, .5),
      0 0 14px var(--ink),
      0 0 32px var(--ink);
  }
}

/* Once a face has been carried home the instruction has been followed, so it
   stops blinking and drops back to a whisper. Two lines pulsing at each other in
   one corner would read as noise, and this is the moment the message below it
   has something to say. */
.hint.saved .do {
  animation: none;
  opacity: .4;
}

/* The message. It stays hidden through the whole struggle and only arrives once
   a face has been carried home — the thing the piece has to say, said at the
   moment it has been earned. Then it breathes, to pull the eye to it. */
.hint .five-d {
  font-size: 30px;
  letter-spacing: .04em;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity .9s ease, transform .9s ease;
}

.hint.saved .five-d {
  opacity: 1;
  transform: none;
  animation: messageBreathe 2.2s ease-in-out .9s infinite;
}

/* Brightness and glow, not size — a line that changes size would shove the
   logo above it around every two seconds. */
@keyframes messageBreathe {
  0%, 100% {
    opacity: .68;
    text-shadow:
      0 1px 2px rgba(42, 0, 9, .62),
      -1px 4px 10px rgba(20, 0, 6, .5);
  }
  50% {
    opacity: 1;
    text-shadow:
      0 1px 2px rgba(42, 0, 9, .62),
      -1px 4px 10px rgba(20, 0, 6, .5),
      0 0 18px var(--ink),
      0 0 42px var(--ink);
  }
}

/* The loading line sits at the bottom centre, just above the flower button. It
   only shows for a moment while the model loads and then fades out, so it never
   competes with anything else. */
#meta {
  position: absolute;
  left: 50%;
  bottom: 124px;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

/* --- guide grid for lining up text: press G to toggle; nothing to do with the render --- */
#gridlines {
  position: absolute;
  inset: var(--grid-pad) calc(var(--grid-pad) - var(--bleed-x));
  pointer-events: none;
  opacity: 0;
  transition: opacity .25s ease;
  background-image:
    repeating-linear-gradient(to right,  var(--grid-line) 0 1px, transparent 1px calc(100% / 12)),
    repeating-linear-gradient(to bottom, var(--grid-line) 0 1px, transparent 1px calc(100% / 12));
  box-shadow: inset -1px -1px 0 0 var(--grid-line);
}

#gridlines.on { opacity: 1; }

/* --- the start button, drawn as a flower opening its petals --- */
#startBtn {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 34px;
  width: 74px;
  height: 74px;
  padding: 12px;
  border: 0;
  background: none;
  color: rgba(253, 252, 253, .62);
  cursor: pointer;
  transition: color .45s ease, transform .45s ease, opacity .6s ease;
}

#startBtn svg { width: 100%; height: 100%; display: block; }

#startBtn:hover {
  color: rgba(255, 214, 236, .95);
  transform: translateX(-50%) scale(1.07) rotate(8deg);
}

/* once pressed it fades, but you can still press again to go back to full bloom */
#startBtn.running { opacity: .22; }
#startBtn.running:hover { opacity: .7; }

/* --- small text in the frame: just the loading line now --- */
#loading {
  font-size: 15px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: rgba(243, 217, 214, .45);
  pointer-events: none;
  user-select: none;
  transition: opacity 1.4s ease;
}

#loading.done  { opacity: 0; }

/* --- "click to start", right above the flower ---
   Drawn in the button's own white rather than the frame's --ink, so the words
   and the flower read as one control instead of as a caption the text layer has
   left lying under it.

   It stands in the same place as the loading line, one above the flower, and
   only arrives once that line has gone — the two never overlap. It breathes,
   the same way the message at the end does, because a still line this quiet at
   the bottom of a full frame is easy to miss. */
#startcue {
  position: absolute;
  left: 50%;
  bottom: 124px;
  /* translateX(-50%) is doing the centring, so it has to be repeated in every
     state this transform is written in, including the .on one below. */
  transform: translateX(-50%) translateY(6px);
  font-family: 'HelveticaLocal', Helvetica, Arial, sans-serif;
  font-size: 15px;
  letter-spacing: .22em;
  white-space: nowrap;
  color: rgba(253, 252, 253, .62);
  pointer-events: none;      /* the flower under it is the thing you press */
  user-select: none;
  opacity: 0;
  transition: opacity .8s ease, transform .8s ease;
}

#startcue.on {
  opacity: 1;
  transform: translateX(-50%);
  animation: cueBreathe 2.6s ease-in-out .8s infinite;
}

/* Brightness and glow, never size: a line that changed size down here would
   look like it was nudging the flower every couple of seconds. */
@keyframes cueBreathe {
  0%, 100% { opacity: .5; text-shadow: none; }
  50%      { opacity: 1;  text-shadow: 0 0 14px rgba(255, 214, 236, .55); }
}

/* --- the explanation, over the whole frame ---
   The i's panel hangs off its own corner because it is a glance. This one is a
   read, so it covers the picture and dims it: the artwork is always moving, and
   words this long cannot be read against it.

   It stretches over the empty strips the same way #type does, so the dimming
   reaches the edge of the window however wide it gets, not just the edge of the
   1920 frame. */
#legend {
  position: absolute;
  inset: 0 calc(-1 * var(--bleed-x));
  z-index: 20;
  display: grid;
  place-items: center;
  padding: 40px;
  background: rgba(14, 0, 7, .74);
  backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity .35s ease;
}

#legend[hidden] { display: none; }
#legend.on { opacity: 1; }

.legend__box {
  /* Every size in here is written against this, so the whole pop-up grows or
     shrinks together and nothing has to be re-balanced by hand. */
  --legend-scale: 1.155;   /* 1.05, then another 10% on top of it */
  position: relative;
  /* Wide enough that the longest meaning in the key — "Bystanders who witness a
     dangerous situation." — holds one line. A key reads as a key when each term
     has one line against it; the moment one wraps, the eye starts hunting for
     where the next term begins. */
  width: min(830px, 100%);
  max-height: 100%;
  overflow-y: auto;
  box-sizing: border-box;
  padding: 52px 60px 54px;
  border-radius: 18px;
  border: 1px solid rgba(203, 228, 92, .3);
  background: rgba(38, 0, 18, .94);
  box-shadow: 0 30px 80px rgba(0, 0, 0, .55);
  color: #FDFCFD;
  font-family: 'HelveticaLocal', Helvetica, Arial, sans-serif;
  /* comes up from under as it fades in, so it arrives rather than appears */
  transform: translateY(14px) scale(.98);
  transition: transform .4s cubic-bezier(.2, .8, .3, 1);
}

#legend.on .legend__box { transform: none; }

.legend__close {
  position: absolute;
  top: 20px; right: 20px;
  width: 38px; height: 38px;
  display: grid; place-items: center;
  padding: 0;
  border: 1.5px solid rgba(203, 228, 92, .45);
  border-radius: 50%;
  background: none;
  color: var(--mark-ink);
  cursor: pointer;
  transition: background .3s ease, color .3s ease, transform .3s ease;
}
.legend__close svg { width: 55%; height: 55%; }

/* Under the pointer it fills, the same as the i and the x on the written page —
   that is a thing you are doing to it. Tabbing to it is not, so a keyboard focus
   gets a ring around the ring and leaves the mark itself alone: the pop-up hands
   the focus here as it opens, and a filled blob sitting there before anyone has
   touched anything reads as a button already pressed. */
.legend__close:hover {
  background: var(--mark-ink);
  color: #3A0016;
  transform: scale(1.08);
}

.legend__close:focus-visible {
  outline: 2px solid var(--mark-ink);
  outline-offset: 4px;
  border-color: var(--mark-ink);
}

.legend__box h2 {
  font-size: calc(15px * var(--legend-scale));
  font-weight: 400;
  letter-spacing: .28em;
  color: var(--mark-ink);
  opacity: .8;
  margin-bottom: 26px;
}

/* Term on the left, meaning on the right, on one line each — the three of them
   are a key to a picture, and a key is quickest read as a column of pairs. */
.legend__key { display: flex; flex-direction: column; gap: 14px; }

.legend__key > div {
  display: grid;
  grid-template-columns: calc(210px * var(--legend-scale)) 1fr;
  gap: 22px;
  align-items: baseline;
  padding-bottom: 14px;
  border-bottom: 1px solid rgba(253, 252, 253, .1);
}

.legend__key dt {
  font-size: calc(19px * var(--legend-scale));
  letter-spacing: .02em;
  color: var(--mark-ink);
}

.legend__key dd, .legend__note, .legend__box p {
  font-size: calc(17px * var(--legend-scale));
  line-height: 1.6;
  letter-spacing: .01em;
}

.legend__key dd { opacity: .88; }

/* The one line of the key that is about something happening rather than
   something being, so it sits on its own under the pairs. */
.legend__note { margin-top: 20px; opacity: .88; }

.legend__box h3 {
  font-size: calc(26px * var(--legend-scale));
  font-weight: 400;
  letter-spacing: .2em;
  color: var(--mark-ink);
  margin: 36px 0 14px;
  padding-top: 30px;
  border-top: 1px solid rgba(203, 228, 92, .25);
}

.legend__box p { opacity: .9; }

/* What to actually do, so it is the one line in here that is not grey. */
.legend__do {
  margin-top: 16px;
  color: var(--mark-ink);
  opacity: 1 !important;
}

/* ---------------------------------------------------------------
   Backdrop mode (?bg=1)

   The written page runs the artwork behind its text. In that job the piece is
   scenery, not something to read or click, so every overlay comes off and only
   the garden is left.
   --------------------------------------------------------------- */
body.as-backdrop #type,
body.as-backdrop #corner-tr,
body.as-backdrop #startBtn,
body.as-backdrop #startcue,
body.as-backdrop #legend,
body.as-backdrop #meta { display: none; }
