/*
 * ---------------------------------------------------------------------
 *  STOL
 * ---------------------------------------------------------------------
 *
 *  What lies on the table beside the box: diskettes, and CD jewel cases
 *  with a disc in them.
 *
 *  Everything is a slab of real millimetres — a 3½" diskette is 94 mm
 *  across and 3.3 mm thick — so the things next to a box keep their size
 *  against it. The jewel case is built from its actual parts: two clear
 *  shells on a hinge, paper inside each, a moulded tray, a disc.
 */


/* ---- what lies in front of the box -------------------------- */

/*
 * Diskettes, tipped back and scattered in front of the box, the
 * way they end up on the table when a box is opened.
 *
 * A sibling of .book rather than a child, so that a diskette is
 * never lit or shaded as though it were a seventh face of the
 * box. The script gives this the same turn as the box every
 * frame: box and table turn together, the way they would if you
 * walked around the table they are both standing on.
 *
 * Anchored at the centre of the box — the same point the box
 * turns about — and carrying its own copy of the size chain, so
 * that "half the box" means this game's box and not :root's.
 */
.props {
    position: absolute;
    left: 50%;
    top: 50%;

    width: 0;
    height: 0;

    transform-style: preserve-3d;

    /* the box is what gets dragged; these are scenery */
    pointer-events: none;

    --box-width:  calc(var(--width)  * var(--box-scale));
    --box-height: calc(var(--height) * var(--box-scale));
    --box-depth:  calc(var(--depth)  * var(--box-scale));

    --half-width:  calc(var(--box-width)  / 2);
    --half-height: calc(var(--box-height) / 2);
    --half-depth:  calc(var(--box-depth)  / 2);
}

/*
 * One diskette: a slab, not a picture of one. Width comes from
 * its real size in millimetres, height from the shape of the
 * scan, thickness from what the thing actually measures. The
 * script writes the transform that lays it down in its place.
 *
 * Built the same way as the box — faces pushed out from the
 * middle with translateZ — because at this angle you are
 * looking almost along the table, and the edge of a diskette is
 * most of what there is to see of it.
 */
.prop {
    position: absolute;
    left: 0;
    top: 0;

    transform-style: preserve-3d;

    /*
     * Props are the things on the table you can pick up: the
     * container above them takes no clicks, they do.
     */
    pointer-events: auto;

    cursor: pointer;

    opacity: 0;

    transition: opacity .35s ease;
}

.prop.is-on {
    opacity: 1;
}

.prop-face,
.prop-edge {
    position: absolute;

    /* the plastic, seen where the label does not reach */
    background: #202126;
}

/*
 * The cut corner of a 3½" diskette.
 *
 * The scan already has it — it is a photograph of a real one —
 * but the slab it is pasted onto is a rectangle, so the plastic
 * shows past the cut as a little square tab. --pc is how deep
 * the cut goes; at 0 every polygon below collapses back into a
 * plain rectangle, which is what a 5¼" sleeve wants.
 *
 * The cut is taken at the bottom left, which is where it lands
 * on a diskette scanned label up, shutter down. A scan that
 * came out turned some other way wants the polygons turned with
 * it.
 */
.prop-face {
    inset: 0;

    /* the scan itself, on the face that is turned upward */
    background-size: 100% 100%;

    clip-path:
        polygon(
            0 0,
            100% 0,
            100% 100%,
            var(--pc) 100%,
            0 calc(100% - var(--pc))
        );
}

/*
 * The underside is the same face turned over, so its own bottom
 * left is the slab's bottom right. Cutting the corner it shares
 * with the top face means cutting the other one here.
 */
.prop-bottom {
    clip-path:
        polygon(
            var(--pc) 0,
            100% 0,
            100% 100%,
            0 100%,
            0 var(--pc)
        );
}

.prop-top {
    transform: translateZ(calc(var(--pd) / 2));
}

/*
 * A booklet scanned open in one go: the right half of the file is its
 * front cover, the left half its back. Drawn at twice the width and
 * slid to one side or the other, so neither half is stretched and the
 * file is used whole — the same trick the box's inside spread uses.
 */
.prop-spread .prop-top {
    background-size: 200% 100%;
    background-position: right center;
}

.prop-spread .prop-bottom {
    background-size: 200% 100%;
    background-position: left center;
}

.prop-bottom {
    background-color: #17181a;

    transform: rotateX(180deg) translateZ(calc(var(--pd) / 2));
}

/* the four edges of the slab, each as thick as the diskette */
.prop-edge {
    width:  var(--pd);
    height: 100%;

    left: calc(50% - var(--pd) / 2);
    top: 0;
}

/*
 * The two edges that run into the cut corner stop short of it,
 * or they would poke out past the chamfer as the square tab
 * this is all here to get rid of.
 */
.prop-left {
    height: calc(100% - var(--pc));

    transform: rotateY(-90deg) translateZ(calc(var(--pw) / 2));
}

.prop-right {
    transform: rotateY(90deg) translateZ(calc(var(--pw) / 2));
}

.prop-head,
.prop-tail {
    width:  100%;
    height: var(--pd);

    left: 0;
    top: calc(50% - var(--pd) / 2);
}

.prop-head {
    transform: rotateX(90deg) translateZ(calc(var(--ph) / 2));
}

.prop-tail {
    width: calc(100% - var(--pc));

    left: var(--pc);

    transform: rotateX(-90deg) translateZ(calc(var(--ph) / 2));
}

/*
 * The cut itself: a fifth edge, standing on the diagonal that
 * closes the gap the other two leave. Laid out flat at the top
 * left, then moved onto the middle of the diagonal, turned to
 * lie along it, and stood up perpendicular to the faces.
 *
 * 1.4142 is the diagonal of a square with sides --pc, and
 * 0.2071 is what centring a strip that long on the corner
 * works out to.
 */
.prop-chamfer {
    width:  calc(var(--pc) * 1.4142);
    height: var(--pd);

    left: 0;
    top: 0;

    transform:
        translate(
            calc(var(--pc) * -0.2071),
            calc(var(--ph) - var(--pc) / 2 - var(--pd) / 2)
        )
        rotateZ(45deg)
        rotateX(90deg);
}

/*
 * A disc. Round faces, no cut corner, and the hub punched clean
 * through both of them so the table shows through the hole the
 * way it does on a real one lying label up.
 *
 * closest-side puts 100% at half the face's width, so --hub can
 * be read straight off the disc: 15 mm through 120 is 12.5%.
 */
.prop-round .prop-face {
    border-radius: 50%;

    clip-path: none;

    -webkit-mask-image:
        radial-gradient(
            circle closest-side,
            transparent var(--hub),
            #000 calc(var(--hub) + 0.5%)
        );

    mask-image:
        radial-gradient(
            circle closest-side,
            transparent var(--hub),
            #000 calc(var(--hub) + 0.5%)
        );
}

.prop-round .prop-shade {
    border-radius: 50%;
}

/*
 * Contact shadow, lying in the same plane as the diskette just
 * under it. It has to be a plane of its own rather than a
 * filter on the slab: a filter flattens what it is applied to,
 * and a flattened slab has no thickness left.
 */
.prop-shade {
    position: absolute;
    inset: -5% -4%;

    background: rgba(0,0,0,.55);

    filter: blur(calc(5px * var(--box-scale)));

    transform: translateZ(calc(var(--pd) / -2 - 1px));
}

/* ---- the jewel case ---------------------------------------- */

/*
 * A CD jewel case, built as the thing it is rather than as a
 * picture of one: two clear plastic shells hinged down the left
 * edge, a paper booklet inside the lid, a tray card under the
 * rear shell, a moulded tray, and a disc sitting in it.
 *
 * Everything is in millimetres against --box-scale, like the
 * boxes. A real case is 142 × 125 × 10, of which the lid is the
 * shallower half.
 *
 * It lies on the table like the diskettes do, so the lid opens
 * upward off the table, which is what opening a case lying flat
 * in front of you does.
 */
.jewel-rear,
.jewel-lid {
    position: absolute;
    inset: 0;

    transform-style: preserve-3d;
}

/*
 * The hinge: the lid turns about the left edge of the case, and
 * nothing else moves. Everything in the lid — plastic, booklet,
 * tabs, its own side walls — is inside this one element, so it
 * all swings together.
 */
.jewel-lid {
    transform-origin: left center;

    transform: rotateY(0deg);

    transition: transform .75s cubic-bezier(.33, .1, .2, 1);
}

.prop-jewel.is-open .jewel-lid {
    transform: rotateY(-112deg);
}

/*
 * Under the pointer the lid lifts a hair — eight degrees, which on a
 * case lying flat is a couple of centimetres of daylight along its
 * open edge. Enough to say it opens, not so much that the table looks
 * like it is coming apart when the mouse crosses it.
 *
 * The same invitation the big boxes with a hinged cover give, and for
 * the same reason: nothing about a closed case says that it opens.
 */
.prop-jewel:hover .jewel-lid {
    transform: rotateY(-8deg);
}

/* already open: the pointer changes nothing */
.prop-jewel.is-open:hover .jewel-lid {
    transform: rotateY(-112deg);
}


/* ---- plastic ------------------------------------------------ */

/*
 * Injection-moulded clear plastic, not glass: almost nothing on
 * the flat, everything at the edges. The gradient is a broad
 * shallow highlight; the inset ring is the moulded lip that
 * runs round the inside of every jewel case.
 */
.jewel-plastic {
    position: absolute;
    inset: 0;

    background:
        linear-gradient(
            118deg,
            rgba(255,255,255,.10) 0%,
            rgba(255,255,255,.02) 26%,
            rgba(255,255,255,0)   46%,
            rgba(255,255,255,.05) 68%,
            rgba(255,255,255,.01) 100%
        ),
        rgba(190,205,225,.05);

    box-shadow:
        inset 0 0 0 calc(var(--jt) / 2) rgba(255,255,255,.14),
        inset 0 0 calc(3px * var(--box-scale)) rgba(255,255,255,.10);
}

/*
 * The fleck of light that says the surface is curved, not flat.
 *
 * It has to fade out on every side, not just along its length. A
 * linear gradient stops at the edge of its own box however faint
 * it has become, and that box is a rectangle: over a printed
 * booklet the cut was lost in the artwork, but over bare plastic
 * it stood in the middle of the lid as a rectangle with hard
 * edges.
 *
 * So the same highlight, put down as an ellipse that reaches
 * nothing on all four sides. It sits high and to the left, where
 * the light is: --light-x is negative and --light-y is up.
 */
.jewel-plastic::after {
    content: "";

    position: absolute;
    inset: 0;

    background:
        radial-gradient(
            ellipse 42% 26% at 34% 22%,
            rgba(255,255,255,.17),
            rgba(255,255,255,.06) 38%,
            rgba(255,255,255,0)   72%
        );
}

/*
 * The walls that give the case its 10 mm, four to a half.
 *
 * Solid, not clear. On a real case the sides are where the
 * plastic is thickest and where the tray and the spines of the
 * card sit right up against it, so nothing shows through them;
 * left transparent they read as a gap in the case rather than
 * as its edge.
 */
.jewel-wall {
    position: absolute;

    background: #16181d;

    box-shadow:
        inset 0 0 0 1px rgba(255,255,255,.09);
}

/* the lid's rim catches more light than the deeper rear half */
.jewel-lid .jewel-wall {
    background: #1d2027;
}

/*
 * The hinge column, full height of the case. The two lighter
 * bands are the pin housings the lid clips into, which is the
 * one detail that says hinge rather than wall.
 */
.jewel-hinge {
    background:
        linear-gradient(
            to bottom,
            #202430 0%,
            #14161c 9%,
            #14161c 16%,
            #1b1e26 22%,
            #1b1e26 78%,
            #14161c 84%,
            #14161c 91%,
            #202430 100%
        );

    box-shadow:
        inset 0 0 0 1px rgba(255,255,255,.10),
        inset 0 0 calc(3px * var(--box-scale)) rgba(0,0,0,.6);
}

.jewel-wall-left,
.jewel-wall-right {
    width: var(--jw-depth);
    height: 100%;

    left: calc(50% - var(--jw-depth) / 2);
    top: 0;
}

/*
 * --jw-shift is where the middle of this half's thickness sits,
 * and it has to be applied before the turn, in the frame of the
 * half rather than in the wall's own. Hence the two translateZ:
 * the first moves the wall to its half, the second pushes it
 * out to the edge it stands on.
 */
.jewel-wall-left {
    transform:
        translateZ(var(--jw-shift))
        rotateY(-90deg)
        translateZ(calc(var(--jw) / 2));
}

.jewel-wall-right {
    transform:
        translateZ(var(--jw-shift))
        rotateY(90deg)
        translateZ(calc(var(--jw) / 2));
}

.jewel-wall-head,
.jewel-wall-tail {
    width: 100%;
    height: var(--jw-depth);

    left: 0;
    top: calc(50% - var(--jw-depth) / 2);
}

.jewel-wall-head {
    transform:
        translateZ(var(--jw-shift))
        rotateX(90deg)
        translateZ(calc(var(--jh) / 2));
}

.jewel-wall-tail {
    transform:
        translateZ(var(--jw-shift))
        rotateX(-90deg)
        translateZ(calc(var(--jh) / 2));
}

/* ---- paper -------------------------------------------------- */

/*
 * The booklet and the tray card. Both are paper sitting behind
 * plastic, inset from the outer edge, which is why a jewel case
 * shows a plastic frame around its artwork.
 */
.jewel-paper {
    position: absolute;

    left: 50%;
    top: 50%;

    background-size: 100% 100%;
    background-color: #0c0d10;
}

/*
 * The tray card is not a booklet page: it runs the full width
 * and height of the case, spines and all, which is why the back
 * of a jewel case has no plastic frame showing the way the
 * front does.
 */
.jewel-card {
    left: 0;
    top: 0;

    width:  100%;
    height: 100%;

    margin: 0;
}

/* ---- the tray ----------------------------------------------- */

/*
 * The moulded tray: a black plastic panel with a round well cut
 * into it, a raised hub in the middle, and the ring of teeth
 * that grip the disc. Drawn rather than photographed, because a
 * photograph of a tray is a picture of a disc lying on it.
 */
.jewel-tray {
    position: absolute;
    inset: 0;

    background:
        radial-gradient(
            circle closest-side at 50% 50%,
            #101216 0 45%,
            #191c22 45% 47%,
            #0e1014 47% 100%
        ),
        #14161b;

    box-shadow:
        inset 0 0 0 calc(1px * var(--box-scale)) rgba(255,255,255,.05);
}

/* the well the disc drops into */
.jewel-well {
    position: absolute;

    left: 50%;
    top: 50%;

    width:  var(--jwell);
    height: var(--jwell);

    margin-left: calc(var(--jwell) / -2);
    margin-top:  calc(var(--jwell) / -2);

    border-radius: 50%;

    background: #0a0b0e;

    box-shadow:
        inset 0 0 calc(4px * var(--box-scale)) rgba(0,0,0,.9),
        0 0 0 calc(1px * var(--box-scale)) rgba(255,255,255,.06);
}

/*
 * The hub, and the teeth around it. The teeth are one repeating
 * conic gradient with a hole masked out of the middle, which is
 * what a ring of moulded fingers amounts to on screen.
 */
.jewel-hub {
    position: absolute;

    left: 50%;
    top: 50%;

    width:  var(--jhub);
    height: var(--jhub);

    margin-left: calc(var(--jhub) / -2);
    margin-top:  calc(var(--jhub) / -2);

    border-radius: 50%;

    background:
        repeating-conic-gradient(
            from 0deg,
            #2b2f37 0deg 9deg,
            #15181d 9deg 18deg
        );

    -webkit-mask-image:
        radial-gradient(
            circle closest-side,
            transparent 0 42%,
            #000 44%
        );

    mask-image:
        radial-gradient(
            circle closest-side,
            transparent 0 42%,
            #000 44%
        );
}

/* ---- the disc ----------------------------------------------- */

.jewel-disc {
    position: absolute;

    /* the one part of the case you can pick up */
    cursor: pointer;

    left: 50%;
    top: 50%;

    width:  var(--jcd);
    height: var(--jcd);

    margin-left: calc(var(--jcd) / -2);
    margin-top:  calc(var(--jcd) / -2);

    border-radius: 50%;

    background-size: 100% 100%;
    background-color: #b7bac2;

    /* the centre hole, punched clean through */
    -webkit-mask-image:
        radial-gradient(circle closest-side, transparent 12%, #000 12.6%);

    mask-image:
        radial-gradient(circle closest-side, transparent 12%, #000 12.6%);
}

/*
 * The sheen off the data side, caught at the edge of the label.
 * Kept faint: a disc under plastic barely catches anything.
 */
.jewel-disc::after {
    content: "";

    position: absolute;
    inset: 0;

    border-radius: 50%;

    background:
        conic-gradient(
            from 210deg,
            rgba(255,255,255,0) 0deg,
            rgba(120,190,255,.16) 40deg,
            rgba(255,255,255,0) 90deg,
            rgba(255,170,120,.12) 200deg,
            rgba(255,255,255,0) 260deg
        );
}
