/* Board — an infinite isometric plane of cards.
 *
 * The plane is a CSS 3D transform: rotateX(60deg) rotateZ(-45deg) with no
 * `perspective`, so it is true isometric rather than a perspective camera.
 * Cards lie on it; a pool of identical tiles is repositioned as you pan, so
 * the board never ends while the asset cost stays fixed.
 */

.board-page { overflow: hidden; height: 100%; }

/* ---------- plane ---------- */

.board-viewport {
    position: fixed;
    inset: 0;
    z-index: 0;
    overflow: hidden;
    cursor: grab;
    touch-action: none;
    /* Dragging across cards would otherwise select their text and the native
       selection drag would hijack the pan. */
    user-select: none;
    -webkit-user-select: none;
    perspective: none;
}

.board-viewport.is-dragging { cursor: grabbing; }

.board-surface {
    position: absolute;
    inset: 0;
    z-index: 0;
    transform-style: preserve-3d;
    /* Set by board.js from the viewport width. A uniform scale commutes with
       the rotation, so it can sit on either side of it. */
    transform: scale(var(--board-zoom, 1));
    transform-origin: 50% 50%;
}

/* Frozen underneath — no panning, no opening a card. The dice is the one
   exception, because it is the way in. */
.is-desktop .board-viewport { pointer-events: none; cursor: default; }

.is-iso .board-surface {
    transform-origin: 50% 50%;
    transform: rotateX(60deg) rotateZ(-45deg) scale(var(--board-zoom, 1));
}

/* Only while entering — otherwise the plane would spin on every load. */
.is-entering .board-surface {
    transition: transform 950ms cubic-bezier(0.32, 0.02, 0.16, 1);
}

/* The board is scenery while the desktop is up: dimmed, and inert apart from
   the dice. 0.35 is as far as it can go and still read as a board — it is also
   what lets white type clear 7:1 on the panels standing over it.

   The scrim lies ON the plane, one per tile, rather than over the screen. A
   screen overlay is a sibling of the whole board, so nothing on the board can
   sit above it; from inside the tile it can sit below the dice and above every
   other card. It also tips with the plane, so it is the same dim in both
   projections. Tiles never overlap, so the joins are invisible. */
.board-tile::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 100;
    background: rgba(44, 48, 49, 0.35);
    opacity: 0;
    pointer-events: none;
    transition: opacity 420ms ease-out;
}

.is-desktop .board-tile::after { opacity: 1; }

.board-tile {
    position: absolute;
    top: 0;
    left: 0;
    will-change: transform;
    transform-style: preserve-3d;
    /* The tile paints its own opaque ground. Without this the body's cloud
       streaks and its screen-space grid show straight through the plane and
       cross it at the wrong angle — three grids fighting at once. */
    background-color: var(--jg-bg);
}

/* The tracks are all different sizes, so the lines are elements rather than a
   repeating gradient. The last line of each axis is skipped: it is the tile's
   own edge, and the next tile draws it as its first. */
.board-line {
    position: absolute;
    background: rgba(255, 255, 255, 0.055);
    pointer-events: none;
}

.board-line-v { top: 0; bottom: 0; width: 1px; }
.board-line-h { left: 0; right: 0; height: 1px; }

/* ---------- cards ---------- */

.bi {
    position: absolute;
    overflow: hidden;
    background: var(--jg-paper);
    border: 1px solid rgba(255, 255, 255, 0.6);
    border-radius: var(--r-md);
    color: var(--jg-ink);
    transform: translateY(0) scale(1);
    /* --in-delay is set per card by board.js, from its distance to the dice. */
    transition: opacity 460ms ease-out var(--in-delay, 0ms),
                transform 520ms cubic-bezier(0.2, 0.9, 0.3, 1.1) var(--in-delay, 0ms),
                box-shadow 0.15s linear;
}

/* First paint only. The cards are permanent now, so this is an arrival rather
   than a state: they assemble outward from the dice as the page opens, nearest
   first, and never animate again. */
.is-booting .bi:not(.bi-piece) {
    opacity: 0;
    transform: translateY(14px) scale(0.97);
}

/* Hard offset shadow, no blur — drawn objects, not floating ones. */
.is-iso .bi { box-shadow: 0 14px 30px rgba(12, 46, 66, 0.22); }

.bi.is-clickable { cursor: pointer; }

.board-viewport:not(.is-dragging) .bi.is-clickable:hover {
    box-shadow: 0 18px 40px rgba(12, 46, 66, 0.34);
    z-index: 500 !important;
}

.bi img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
    pointer-events: none;
    -webkit-user-drag: none;
}

/* --- written section --- */

.bi-note {
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 26px 28px;
    background: var(--jg-paper);
}

.bi-note .n-num {
    font-family: var(--jg-mono);
    font-size: 13px;
    letter-spacing: 0.18em;
    color: var(--jg-accent);
}

.bi-note .n-head {
    font-family: var(--jg-display);
    font-weight: 700;
    font-size: 30px;
    line-height: 1.14;
    letter-spacing: -0.01em;
}

.bi-note .n-body {
    font-size: 16px;
    line-height: 1.6;
    color: var(--jg-muted);
}

/* --- quote --- */

.bi-quote {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: 14px;
    padding: 24px 26px;
}

/* ---------- faces ----------
   Not one voice repeated 39 times. A card's face comes from how much there is
   to say — a four-word line gets set enormous, a long one gets set small and
   quiet — and its size is solved per card rather than per face, because one
   coefficient for a whole group has to satisfy the tightest card in it and
   wastes every card that is roomier.

   --q-size is written per card by board.js, from a wrap simulation that checks
   the line count and the attribution against the actual box. */

.bi-quote .q-text {
    font-family: var(--jg-display);
    font-size: var(--q-size, 21px);
    font-weight: 500;
    line-height: 1.34;
}

/* Four words, set as loud as the card allows. */
.q-shout .q-text {
    font-family: 'Anton', var(--jg-display), sans-serif;
    font-weight: 400;
    line-height: 0.92;
    letter-spacing: -0.005em;
    text-transform: uppercase;
}

.q-script .q-text {
    font-family: 'Caveat', var(--jg-display), cursive;
    font-weight: 700;
    line-height: 1.05;
}

/* Centred and letterspaced — the one that reads as a statement rather than
   as a caption. */
.q-caps {
    align-items: center;
    text-align: center;
}

.q-caps .q-text {
    font-family: 'Inter', system-ui, sans-serif;
    font-weight: 600;
    line-height: 1.4;
    letter-spacing: 0.07em;
    text-transform: uppercase;
}

.q-serif .q-text {
    font-family: 'Instrument Serif', Georgia, serif;
    font-style: italic;
    font-weight: 400;
    line-height: 1.18;
}

.q-display .q-text {
    font-family: var(--jg-display), sans-serif;
    font-weight: 700;
    line-height: 1.3;
}

/* Full strength, never dimmed. Fading it to 0.72 read nicely on cream and
   dropped the clay tone to 3.1:1 — that tone has only 4.9:1 to begin with, so
   there is no headroom to spend. The hierarchy is size and tracking instead. */
.bi-quote .q-attr {
    font-family: var(--jg-mono);
    font-size: 11px;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: currentColor;
}

/* ---------- tones ----------
   A card is one of seven. Every pairing was measured against its own ground:
   4.9:1 at worst (clay) and 12.0:1 at best (paper and ink).

   The dark tones sit close to the board's own grey — 1.3 to 1.8:1 — so they
   read as cards by their edge and their shadow rather than by fill. There is
   no colour that carries white type and also separates from a dark ground;
   one or the other has to give, and the edge is what pays for it. */

.t-paper     { background: #f2efe4; color: #2a2e2b; }
.t-paperblue { background: #f2efe4; color: #2f5d8c; }
.t-blue      { background: #2f5d8c; color: #f4f6f6; }
.t-ink       { background: #22302e; color: #f2efe4; }
.t-amber     { background: #e8a33d; color: #2a2e2b; }
.t-teal      { background: #2b6f6b; color: #f4f6f6; }
.t-clay      { background: #cf8b5e; color: #2a2e2b; }

.t-blue, .t-ink, .t-teal { border-color: rgba(255, 255, 255, 0.3); }

/* --- image --- */

.bi-img {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 18px;
    background: var(--jg-panel);
}

/* --- swatch --- */

.bi-swatch {
    display: flex;
    align-items: flex-end;
    padding: 14px;
}

.bi-swatch .sw-hex {
    font-family: var(--jg-mono);
    font-size: 12px;
    background: var(--jg-paper);
    border: 2px solid var(--jg-ink);
    padding: 4px 8px;
}

/* --- the dice card ---
   One square like everything else, lying WITH the plane. Standing it upright
   made it a cut-out: a flat billboard with a hard edge where it met the
   ground. Lying down, it tips as you enter — the dice lay themselves onto the
   board rather than being swapped onto it. */

.bi-piece {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 22px;
    background: var(--jg-panel);
    pointer-events: none;
    /* A real lift, not just a z-index. The plane is a preserve-3d context, so
       its children are sorted by depth and z-index is at best a tiebreaker
       between coplanar ones — 1px of Z puts the dice in front of the scrim
       whichever rule the browser applies. Too small to see, at any angle. */
    transform: translateZ(1px);
}

/* Clickable while the desktop is up, and only then — it is the way into the
   board. It sits wherever the dice actually is, so it stays correct after you
   have panned somewhere else and called the desktop back. */
.is-desktop .bi-piece {
    pointer-events: auto;
    cursor: pointer;
}

/* Above the scrim, at z-index 600 set in board.js against the 100 the scrim
   sits at. It is the one thing on the board that is still live, so it is the
   one thing that is not dimmed — it reads as lit rather than as scenery. */

/* Shown until the board has been entered once, then never again. */
.bi-piece-cue {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 14px;
    text-align: center;
    font-family: var(--jg-mono);
    font-size: 11px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--jg-muted);
    opacity: 0;
    transition: opacity 300ms ease-out;
}

.is-virgin.is-desktop .bi-piece-cue { opacity: 1; }
.is-desktop .bi-piece:hover .bi-piece-cue { color: var(--jg-accent); }

/* --- placeholder --- */

.bi-ph {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 18px;
    text-align: center;
    /* A wash rather than nothing: a transparent card reads as a hole in the
       board rather than a slot waiting for an image. */
    background: rgba(255, 255, 255, 0.3);
    border-style: dashed;
    border-color: rgba(255, 255, 255, 0.65);
}

.bi-ph .ph-label {
    font-family: var(--jg-mono);
    font-size: 13px;
    color: var(--jg-muted);
}

.bi-ph .ph-meta {
    font-family: var(--jg-mono);
    font-size: 11px;
    color: var(--jg-dim);
}

/* ---------- hint ---------- */

.board-hint {
    position: fixed;
    z-index: 60;
    bottom: 1.5rem;
    left: 50%;
    transform: translateX(-50%);
    margin: 0;
    padding: 0.55rem 1rem;
    background: var(--jg-paper);
    border: 2px solid var(--jg-ink);
    font-family: var(--jg-mono);
    font-size: 0.66rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--jg-muted);
    pointer-events: none;
    transition: opacity 0.5s ease;
}

.board-hint.is-hidden,
.is-desktop .board-hint { opacity: 0; }
