/* ==========================================================================
   UNIQorn — ink figures
   assets/illustration.css + assets/illustration.js

   十张水彩被从一张画里取出来，不是做成画廊，而是各自落在一页上。它们到达的
   方式和这个站点上所有东西到达的方式一样：先有一条线走到它，然后画面才起来。

   One number
   ----------
   `--ink-p` lives on the figure and runs 0 -> 1. **Its default is 1, the
   finished state**, so anything that never runs the script — no JS, print, a
   browser that drops the file — already shows the plate at full strength.
   This is the same rule `--draw-p` (draw.css) and `--read-p` (learn.css)
   follow; it is the site's grammar for "the animation is decoration, the
   result is the page".

   Why there is no background box
   ------------------------------
   Each plate is an SVG whose paper is genuinely transparent, so the strokes
   land on the page's own paper instead of on a picture of paper. That single
   fact is what makes these read as part of the page rather than as something
   pasted onto it, and it is why nothing here sets a background, a border or a
   shadow. The moment this file gives a figure a panel, the whole point is lost.
   ========================================================================== */

.ink-fig {
  /* The finished state. The script re-arms this to 0; nothing else may. */
  --ink-p: 1;
  --ink-hair: .34;

  position: relative;
  display: flex;
  align-items: center;
  gap: 0;
  margin: clamp(56px, 9vw, 132px) 0;
  width: 100%;
}

/* ---------- the line ---------- */

/*
 * The lead is the flexible half. It stretches from the page's own gutter to
 * the plate, so the line always starts off the edge of the text column — a
 * line that started at the column edge would read as a rule, not as a thread
 * coming in from somewhere else.
 */
.ink-fig__lead {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  height: clamp(96px, 15vw, 210px);
}

.ink-fig__svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.ink-fig__belly,
.ink-fig__stem {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
  /*
   * A hairline at any size: the svg is stretched over the lead with
   * `preserveAspectRatio="none"`, so without `vector-effect` the stroke would
   * be scaled by a different factor on each axis and stop being 1px.
   *
   * `--ink-len` is written by illustration.js from the path's own measured
   * screen length, which is what a non-scaling stroke reads its dash pattern
   * in. Without the script the property is absent, the dash is `none`, and the
   * line is simply solid — never dashed, and never missing.
   */
  vector-effect: non-scaling-stroke;
  stroke-dasharray: var(--ink-len, none);
  stroke-dashoffset: calc(var(--ink-len, 0) * (1 - var(--ink-p)));
}

.ink-fig__stem { stroke-width: 1; }
.ink-fig__belly { stroke-width: 2.4; }

/*
 * The anchor. A single indigo point where the line comes to rest, at the edge
 * of the plate. It is the same mark the cover and the space landings end on,
 * and it is what makes the line look like it arrived somewhere on purpose.
 */
.ink-fig__anchor {
  position: absolute;
  top: 58%;
  width: 5px;
  height: 5px;
  margin: -2.5px 0 0 -2.5px;
  border-radius: 50%;
  background: var(--indigo);
  opacity: calc(var(--ink-p) * .55);
}

/* ---------- the plate ---------- */

/*
 * Three composited properties, no layout property. `filter` — the edge
 * arriving — runs with p; `scale` and `translate` bring it up out of the page.
 * Nothing here touches width, height, margin or position, so the page does not
 * reflow while the plate comes in.
 */
.ink-fig__plate {
  position: relative;
  flex: 0 0 auto;
  display: block;
  width: 100%;
  aspect-ratio: var(--plate-ar, 1);
}

/*
 * The plate itself.
 *
 * No background, no border, no shadow, and no blend mode — and that is not an
 * oversight. The artwork arrives with a real alpha channel: the four corners
 * are alpha 0 and most of the plate is fully transparent. There is no sheet of
 * paper in the file to hide, so there is nothing to blend away. Every rule
 * that was tried here before (multiply, a feathered mask) existed only to
 * patch a plate that had been cut to a tile instead of to the drawing. These
 * are cut to the drawing.
 *
 * Three composited properties, no layout property, so the page does not
 * reflow while the plate comes up.
 */
.ink-fig__art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  opacity: calc(var(--ink-p) * var(--ink-p));
  filter: blur(calc((1 - var(--ink-p)) * 11px));
  scale: calc(.955 + var(--ink-p) * .045);
  translate: 0 calc((1 - var(--ink-p)) * 20px);
}

/* ---------- the plate's own light ---------- */

/*
 * Where the paint already has a spark or a red point, it is re-lit. The marks
 * come from the artwork export's own anchors, placed in percent of the plate
 * so they stay on their mark at any size. Decoration only: the figure they sit
 * in is aria-hidden and they carry no text.
 */
.ink-light {
  position: absolute;
  width: 34px;
  height: 34px;
  margin: -17px 0 0 -17px;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
}
.ink-light--star {
  background: radial-gradient(closest-side, rgba(255, 246, 223, .95), rgba(255, 246, 223, .48) 32%, rgba(255, 246, 223, 0) 100%);
  animation: ink-spark 6.4s ease-in-out infinite;
}
.ink-light--red {
  background: radial-gradient(closest-side, rgba(229, 160, 155, .74), rgba(229, 160, 155, 0) 100%);
  animation: ink-glow 5.2s ease-in-out infinite;
}
@keyframes ink-spark {
  0%, 100% { opacity: .14; transform: scale(.82); }
  50%      { opacity: .95; transform: scale(1.10); }
}
@keyframes ink-glow {
  0%, 100% { opacity: .12; transform: scale(.86); }
  50%      { opacity: .70; transform: scale(1.14); }
}

/* ---------- alignment ---------- */

/*
 * `right` means the plate sits on the right and the line comes in from the
 * left, so the lead is painted first. `left` is the mirror, and the DOM order
 * is reversed rather than the geometry — one path definition serves both.
 */
.ink-fig--right  { flex-direction: row; }
.ink-fig--left   { flex-direction: row-reverse; }
.ink-fig--center { flex-direction: column; }

.ink-fig--right .ink-fig__anchor { right: 0; }
.ink-fig--left  .ink-fig__anchor { left: 0; }

.ink-fig--center .ink-fig__lead {
  width: 100%;
  height: clamp(72px, 11vw, 150px);
}
.ink-fig--center .ink-fig__anchor {
  left: 50%;
  top: auto;
  bottom: 0;
}

/* ---------- sizes ---------- */

/*
 * Percentages of the content column, not fixed pixels: the plates have wildly
 * different aspect ratios, and what has to stay constant is how much of the
 * column they take, not how many pixels tall they are.
 */
.ink-fig--sm .ink-fig__plate { max-width: 30%; }
.ink-fig--md .ink-fig__plate { max-width: 44%; }
.ink-fig--lg .ink-fig__plate { max-width: 58%; }

/*
 * A tall plate at half the column is already enormous. The unicorn and the
 * moon-phases are 1:1.9 and 1:1.9 — cap them by height as well, or they run
 * off the bottom of the viewport on a laptop.
 */
/*
 * A tall plate at half the column is already enormous. The tall ones in this
 * set run 1:1.5 and 1:2.5 — cap them by height as well, or they run off the
 * bottom of a laptop.
 */
.ink-fig--moon .ink-fig__plate,
.ink-fig--journey .ink-fig__plate,
.ink-fig--unicorn .ink-fig__plate { max-height: min(70vh, 600px); }

/* ---------- the draw ---------- */

/*
 * Everything hangs off `.is-live`, which illustration.js adds only after it
 * has measured the path and only when motion is welcome. If the script never
 * runs, or the visitor has asked for reduced motion, the plate is already
 * there — arrived, not mid-arrival.
 */
.ink-fig.is-live .ink-fig__art,
.ink-fig.is-live .ink-fig__stem,
.ink-fig.is-live .ink-fig__belly,
.ink-fig.is-live .ink-fig__anchor {
  /* Progress is written per frame by the script; no transition here, or the
     two clocks fight and the plate lags a frame behind the line. */
  will-change: opacity, filter, scale, translate;
}

/* ---------- responsive ---------- */

@media (max-width: 860px) {
  /*
   * Below the tablet breakpoint the column is narrow enough that a side-by-side
   * lead leaves the plate too small to read. The line goes above the plate
   * instead — the same gesture, rotated — and the plate takes the full width.
   */
  .ink-fig--left,
  .ink-fig--right { flex-direction: column; }

  .ink-fig--left .ink-fig__lead,
  .ink-fig--right .ink-fig__lead {
    width: 100%;
    height: clamp(64px, 16vw, 110px);
    order: -1;
  }

  .ink-fig--left .ink-fig__anchor,
  .ink-fig--right .ink-fig__anchor {
    left: 50%;
    right: auto;
    top: auto;
    bottom: 0;
  }

  .ink-fig--sm .ink-fig__plate,
  .ink-fig--md .ink-fig__plate,
  .ink-fig--lg .ink-fig__plate { max-width: 100%; }

  .ink-fig--moon .ink-fig__plate,
  .ink-fig--journey .ink-fig__plate,
  .ink-fig--unicorn .ink-fig__plate { max-height: 56vh; }
}

@media (prefers-reduced-motion: reduce) {
  /*
   * Not "animation: none" — that would leave the plate at whatever the inline
   * progress happened to be. The finished state is asserted, which is the same
   * thing the missing-script path does.
   */
  .ink-fig { --ink-p: 1 !important; }
}

/* ==========================================================================
   A door that is a plate

   On the tarot landing, the free-draw entrance is not a row of text with a
   picture beside it — the picture is the entrance. The whole plate is inside
   the link, so pressing the drawing goes where the drawing points.

   ---------------------------------------------------------------------------
   One text column, and the paintings stand to its left

   The list already has a column doing two jobs:

       --door-in = the drawn line's column  =  the doors' left inset

   `--fan-gutter` is that column, and the hairline above each row begins at its
   right edge, so every rule reads as growing out of the line.

   A painting is wider than that column, and the obvious move — push the row's
   text right to make room — is wrong. It gives the painted row a different
   left edge from every other row, and a list whose labels do not share a left
   edge stops reading as a list. The eye finds that before it finds anything
   else, and no amount of care further down repairs it.

   So the list gets a second column instead, and every row uses it whether or
   not it has a painting:

       --door-text = --door-in + the painting column + the gap

   The painting is taken out of the flow and set into the first column, so it
   can be any height without disturbing the text. Rows without a painting keep
   that column empty. That emptiness is not a hole; it is the place the
   painting will be, and it is why this rule set is worth having before the
   other eight paintings exist.

   ---------------------------------------------------------------------------
   The painting's width is computed, not measured

   `aspect-ratio` cannot take part in a CSS multiplication, so the ratio is
   also written as a plain number (`--plate-ratio`) by PHP. Width is then
   `height x ratio`, which means the rule's start point is exact at every
   viewport with no measuring script and no reflow.
   ========================================================================== */

/*
 * The column is exactly as wide as the widest painting that will stand in it.
 *
 * Both heights are declared here, on the list, and each door picks one. The
 * column is then derived from the taller of the two times the set's ratio —
 * 3:4, which is the canvas every new plate is drawn to. Nothing is guessed and
 * nothing is left over: before this, the column was a fixed clamp and the
 * narrow plates sat in it with a hand's width of dead space beside them, which
 * is the kind of gap that reads as a mistake rather than as air.
 *
 * A plate that is not 3:4 — the moon column is 1:2.5 — simply sits narrower in
 * the column. That is correct: the column belongs to the widest painting, and
 * a narrower one is allowed to breathe inside it.
 */
.space-landing .space-doors.has-plates {
  --plate-h-secondary: clamp(176px, 17vw, 248px);
  --plate-h-primary:   clamp(240px, 24vw, 344px);
  --plate-col: calc(var(--plate-h-primary) * .75);
  --plate-gap: clamp(26px, 3.2vw, 60px);
}

/*
 * Every row in a painted list shares one text column, painted or not.
 *
 * `--door-text` is composed *on the door*, not on the list, and that is not a
 * style choice. A custom property is substituted where it is used, so a value
 * written on an ancestor can only refer to properties that ancestor can see —
 * and `--door-in` is declared on `.door` itself, because it needs the door's
 * own `--i`. Written on the list, `--door-text` resolved against nothing, the
 * whole `calc()` became invalid, and the declaration was dropped silently: the
 * text looked right for the wrong reason and the rules never moved.
 *
 * The rule above each row moves with it — same 1px, same colour, one number
 * changed.
 */
.space-landing .space-doors.has-plates .door {
  --door-text: calc(var(--door-in) + var(--plate-col) + var(--plate-gap));
  padding-inline: var(--door-text) var(--space-3);
}
.space-landing .space-doors.has-plates .door::before,
.space-landing .space-doors.has-plates .door:last-child::after {
  left: var(--door-text);
}

/*
 * A painted row is taller than its text. The painting is out of the flow, so
 * the row has to be told how tall to be — otherwise the drawing spills over
 * the rule below it.
 */
.space-landing .door--plate {
  --plate-h: var(--plate-h-secondary);
  min-height: calc(var(--plate-h) + clamp(60px, 7.2vw, 116px));
}
.space-landing .door--plate.is-primary {
  --plate-h: var(--plate-h-primary);
}

/*
 * The painting itself. Height drives, width follows from the ratio, so the
 * drawing keeps its own proportions and is never letterboxed — which matters
 * because the light marks are placed in percent of this box.
 */
.space-landing .door--plate .door-plate {
  position: absolute;
  left: var(--door-in);
  top: 50%;
  translate: 0 -50%;
  display: block;
  height: var(--plate-h);
  width: calc(var(--plate-h) * var(--plate-ratio, 1));
}

.space-landing .door--plate .door-plate img {
  display: block;
  width: 100%;
  height: 100%;
  transition: translate .9s var(--ease-standard), scale 1.4s var(--ease-standard);
}

/*
 * The rule, re-hung.
 *
 * On an unpainted row the rule begins at the text column, as it always has.
 * On a painted row it begins at the painting's right edge instead and fades in
 * over its first ~70px — so it does not merely start beside the drawing, it
 * comes out of it. The two are deliberately different: a rule should start
 * where its row's content starts, and a painting is content.
 */
/* Declared after the shared rule above, at the same specificity, so it wins. */
.space-landing .space-doors.has-plates .door--plate::before,
.space-landing .space-doors.has-plates .door--plate:last-child::after {
  left: calc(var(--door-in) + var(--plate-h) * var(--plate-ratio, 1));
  background: linear-gradient(90deg, rgba(30, 58, 95, 0), var(--line) 72px);
}
.space-landing .space-doors.has-plates .door--plate:last-child::after {
  background: var(--line);
}

/*
 * The painting arrives the way everything else on this site arrives: it comes
 * up out of the paper while the rule is already there. Same one number
 * (`--ink-p`) as the page plates, driven by the same observer — a door and an
 * illustration are the same painting used two ways, and they must not arrive
 * differently.
 */
.space-landing .door--plate .door-plate {
  opacity: calc(var(--ink-p, 1) * var(--ink-p, 1));
  filter: blur(calc((1 - var(--ink-p, 1)) * 10px));
  translate: 0 calc(-50% + (1 - var(--ink-p, 1)) * 22px);
}

/* The drawing answers the press the same way the label does. */
.space-landing .door--plate:hover .door-plate img,
.space-landing .door--plate:focus-visible .door-plate img {
  translate: 0 -8px;
  scale: 1.03;
}

.space-landing .door--plate .door-body { align-self: center; }

@media (max-width: 700px) {
  /*
   * Below the tablet breakpoint there is no room for a painting and a
   * paragraph side by side. The list drops to one column and the painting
   * moves above its own text — the same object, the same proportions, stacked.
   */
  .space-landing .space-doors.has-plates {
    --plate-col: 0px;
    --plate-gap: 0px;
  }
  .space-landing .door--plate {
    --plate-h: clamp(180px, 46vw, 260px);
    padding-block-start: calc(var(--plate-h) + var(--space-5));
  }
  .space-landing .door--plate .door-plate {
    left: var(--door-in);
    top: 0;
    translate: 0 0;
    width: auto;
    max-width: calc(100% - var(--door-in));
    aspect-ratio: var(--plate-ar, 1);
  }
  .space-landing .door--plate .door-plate img { width: 100%; height: 100%; object-fit: contain; }
  .space-landing .door--plate::before,
  .space-landing .door--plate:last-child::after { left: var(--door-in); background: var(--line); }
  .space-landing .door--plate .door-go { display: none; }
}
