/* =========================================================
   LOUPYL
   Two typefaces, sitewide. No CSS-drawn edges anywhere.
   The only visible frames on this site are your PNGs.
   ========================================================= */


/* ---------------------------------------------------------
   FONTS
   Director  -> every paragraph, list, caption, body word
   Terminal  -> every heading, nav item, link, label
   No third face. No per-section variation.
   --------------------------------------------------------- */

@font-face {
    font-family: "Director";
    src: url("fonts/Director-Regular.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "Terminal Grotesque";
    src: url("fonts/terminal-grotesque_open.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}


/* ---------------------------------------------------------
   TOKENS
   --------------------------------------------------------- */

:root {
    --body:     "Director", Georgia, "Times New Roman", serif;
    --display:  "Terminal Grotesque", "Director", Georgia, serif;

    --void:       #050505;
    --ink:        #b4b4b4;
    --ink-bright: #e8e8e8;
    --ink-dim:    #6e6e6e;

    /* Feathered dark pools instead of bordered boxes. */
    --pool:       rgba(4, 4, 4, 0.86);
    --pool-haze:  0 0 90px 55px rgba(4, 4, 4, 0.86);

    --gutter: clamp(18px, 4vw, 48px);
}


/* ---------------------------------------------------------
   PAGE
   --------------------------------------------------------- */

*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    scroll-behavior: smooth;
}

body {
    margin: 0;
    padding: 0;
    color: var(--ink);
    background: var(--void);
    background-image: radial-gradient(
        ellipse 90% 65% at 50% 22%,
        #1c1c1c 0%,
        #0c0c0c 45%,
        #020202 100%
    );
    background-attachment: fixed;
    font-family: var(--body);
    font-size: clamp(15px, 0.55vw + 13px, 18px);
    line-height: 1.85;
    overflow-x: hidden;
    -webkit-font-smoothing: antialiased;
}


/* Every image on this site is black and white, no exceptions.
   Several source files are in full colour; this is the single
   gate that keeps the palette honest. Anything overriding
   `filter` below must restate grayscale(1) - filters replace,
   they do not stack. */

img {
    filter: grayscale(1);
}

/* To let one image keep its colour, put class="in-colour" on it.
   The rule that does it lives at the very bottom of this file -
   see OVERRIDES. */


/* ---------------------------------------------------------
   TYPE
   Hierarchy comes from size, tracking and caps only.
   --------------------------------------------------------- */

h1, h2, h3, h4, h5, h6,
nav a,
.label {
    font-family: var(--display);
    font-weight: 400;
    text-transform: uppercase;
    color: var(--ink-bright);
    line-height: 1.15;
}

h2 {
    font-size: clamp(1.7rem, 3.2vw, 2.4rem);
    letter-spacing: 0.22em;
    margin: 0 0 1.4em;
    text-indent: 0.22em; /* balances the trailing letter-space */
    text-align: center;
    color: #d4d4d4;
}

h3 {
    font-size: clamp(1.05rem, 1.6vw, 1.25rem);
    letter-spacing: 0.18em;
    margin: 2.6em 0 0.9em;
    color: #c2c2c2;
}

p {
    margin: 0 0 1.5em;
    text-wrap: pretty;
}

p:last-child {
    margin-bottom: 0;
}

strong {
    font-weight: 400;
    color: var(--ink-bright);
}

::selection {
    background: #d8d8d8;
    color: #000;
}


/* ---------------------------------------------------------
   LINKS
   No underline, no border. Presence is signalled with
   weight of light, the way a print caption would.
   --------------------------------------------------------- */

a {
    font-family: var(--display);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    font-size: 0.92em;
    color: #cfcfcf;
    text-decoration: none;
    transition: color 160ms ease, text-shadow 160ms ease;
}

a:hover,
a:focus-visible {
    color: #fff;
    text-shadow: 0 0 14px rgba(255, 255, 255, 0.45);
}

/* Visible keyboard focus that isn't a drawn rectangle. */
a:focus-visible {
    outline: none;
    background: #e8e8e8;
    color: #000;
    text-shadow: none;
}


/* ---------------------------------------------------------
   SHELL
   --------------------------------------------------------- */

.site {
    position: relative;
    width: 100%;
    max-width: 1100px;
    margin: 0 auto;
    padding: 0 var(--gutter);
    z-index: 1;
}


/* ---------------------------------------------------------
   BACKGROUND PLATES
   --------------------------------------------------------- */

.backdrop {
    position: fixed;
    z-index: -5;
    opacity: 0.09;
    filter: grayscale(1);
    pointer-events: none;
    user-select: none;
}

.backdrop--ship {
    width: min(800px, 70vw);
    right: -12vw;
    bottom: -14vh;
}

.backdrop--castle {
    height: 95vh;
    left: -10vw;
    bottom: -8vh;
}


/* =========================================================
   NAMEPLATE
   The fix: the artwork and the lettering live in ONE box
   locked to the PNG's own 950x622 ratio, and the words ride
   a curved path cut to the slant of the scroll. Scale the
   page to any width and the type stays welded to the ribbon
   - no pixel offsets, no per-breakpoint nudging.
   ========================================================= */

.masthead {
    position: relative;
    padding: clamp(24px, 5vw, 60px) 0 clamp(8px, 2vw, 20px);
}

.nameplate {
    position: relative;
    z-index: 2;
    width: min(880px, 100%);
    aspect-ratio: 950 / 622;
    margin: 0 auto;
}

.nameplate__art,
.nameplate__type {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
}

.nameplate__art {
    object-fit: contain;
    pointer-events: none;
}

.nameplate__type {
    overflow: visible;
    pointer-events: none;
}

/* Lettering carved into the ribbon: light fill, dark bed. */
.nameplate__title {
    font-family: var(--display);
    font-size: 96px;
    letter-spacing: 12px;
    fill: #e4e4e4;
    stroke: #000;
    stroke-width: 7;
    paint-order: stroke fill;
    stroke-linejoin: round;
}

.nameplate__sub {
    font-family: var(--display);
    font-size: 17px;
    letter-spacing: 6px;
    fill: #9a9a9a;
    stroke: #000;
    stroke-width: 3.5;
    paint-order: stroke fill;
}

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}


/* ---------------------------------------------------------
   NAV
   Fades in and out of the dark. No rules above or below.
   --------------------------------------------------------- */

nav {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: clamp(10px, 2.5vw, 34px);
    padding: 22px var(--gutter) 26px;
    background: radial-gradient(
        ellipse 70% 130% at 50% 50%,
        rgba(0, 0, 0, 0.85) 0%,
        rgba(0, 0, 0, 0) 72%
    );
}

nav a {
    font-size: clamp(0.72rem, 1.4vw, 0.85rem);
    letter-spacing: 0.28em;
    color: #b0b0b0;
    padding: 4px 2px;
}

nav a:hover {
    color: #fff;
}


/* ---------------------------------------------------------
   SECTIONS
   A section is a pool of darkness with a feathered halo,
   never a rectangle. The halo is the same colour as the
   fill, so there is no edge to see.
   --------------------------------------------------------- */

section {
    position: relative;
    margin: clamp(90px, 13vw, 180px) auto;
    padding: clamp(28px, 5vw, 56px);
    background: var(--pool);
    box-shadow: var(--pool-haze);
}

section > * {
    position: relative;
    z-index: 5;
}

.measure {
    max-width: 62ch;
    margin: 0 auto;
    text-align: center;
}

.measure--left {
    text-align: left;
}


/* Section artwork. Sits behind the words, bleeds past the pool. */
.art {
    position: absolute;
    z-index: 2;
    opacity: 0.5;
    filter: grayscale(1) contrast(1.08);
    pointer-events: none;
    user-select: none;
}

.art--knight   { width: min(360px, 40vw); right: -14%; bottom: -10%; }
.art--cavalry  { width: min(410px, 44vw); left: -18%;  bottom: -12%; }
.art--pirate   { width: min(620px, 62vw); left: -22%;  bottom: -12%; opacity: 0.5; }
.art--ship     { width: min(700px, 66vw); right: -30%; top: -14%;    opacity: 0.32; }
.art--tower    { height: min(700px, 74vw); left: -20%; bottom: -22%; opacity: 0.28; }

/* Engraved swallows drifting out of the top corner. */
.art--birds {
    width: min(340px, 38vw);
    right: -8%;
    top: -9%;
    opacity: 0.42;
    filter: grayscale(1) contrast(1.3);
}

/* A band of surf running the full width, bled off both edges so
   it has no ends to see. It sits under the text, not behind it -
   .has-band reserves the clearance. */
.art--waves {
    width: 128%;
    left: -14%;
    bottom: -5%;
    opacity: 0.28;
    filter: grayscale(1) contrast(1.25) brightness(0.95);
}

/* Room beneath the words for a bleeding band. */
.has-band {
    padding-bottom: clamp(200px, 24vw, 340px);
}

/* Engraved cloud bank spread wide behind the scroll. Kept very
   faint - it is weather behind the title, not a second subject. */
.art--skyline {
    width: 132%;
    left: -16%;
    bottom: -6%;
    z-index: 0;
    opacity: 0.16;
    filter: grayscale(1) contrast(1.3);
}

/* Storm head rising out of the top right of the photographs. */
.art--anvil {
    width: min(560px, 58vw);
    right: -20%;
    top: -12%;
    opacity: 0.22;
    filter: grayscale(1) contrast(1.05) brightness(0.95);
}

/* The hill, grounded on the bottom left. */
.art--tree {
    width: min(480px, 52vw);
    left: -19%;
    bottom: -11%;
    opacity: 0.34;
    filter: grayscale(1) contrast(1.15);
}

/* Flock breaking away from the archive. */
.art--flock {
    width: min(300px, 34vw);
    left: -10%;
    top: -6%;
    opacity: 0.4;
    filter: grayscale(1) contrast(1.2);
}

/* Surf closing the page out under the footer. */
.art--surf {
    width: 126%;
    left: -13%;
    bottom: -14%;
    opacity: 0.2;
    filter: grayscale(1) contrast(1.2) brightness(0.95);
}

/* The knights and ships are cut-out silhouettes, so they end on
   their own outline. These five are full-bleed scenes that end on
   a straight crop instead, which puts a rectangle on the page.
   Feathering the borders lets them dissolve into the dark. */
.art--skyline,
.art--anvil,
.art--tree,
.art--waves,
.art--surf {
    -webkit-mask-image:
        linear-gradient(to right,  rgba(0,0,0,0) 0%, #000 12%, #000 88%, rgba(0,0,0,0) 100%),
        linear-gradient(to bottom, rgba(0,0,0,0) 0%, #000 12%, #000 88%, rgba(0,0,0,0) 100%);
    -webkit-mask-composite: source-in;
    mask-image:
        linear-gradient(to right,  rgba(0,0,0,0) 0%, #000 12%, #000 88%, rgba(0,0,0,0) 100%),
        linear-gradient(to bottom, rgba(0,0,0,0) 0%, #000 12%, #000 88%, rgba(0,0,0,0) 100%);
    mask-composite: intersect;
}


/* ---------------------------------------------------------
   LISTS
   --------------------------------------------------------- */

ul {
    list-style: none;
    margin: 0;
    padding: 0;
}

.measure ul li {
    margin: 0.35em 0;
}

.measure ul li::before {
    content: "\2022";
    color: var(--ink-dim);
    margin-right: 0.8em;
}


/* =========================================================
   GALLERY GRID
   ========================================================= */

/* Two up. Both photographs are portrait, so the tiles are too.
   Fixed at two columns rather than auto-fit: with a small
   collection auto-fit leaves a hole in the last row. */
.grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: clamp(14px, 2.5vw, 28px);
    margin: clamp(30px, 5vw, 50px) 0 0;
}

.grid figure {
    position: relative;
    margin: 0;
    overflow: hidden;
    background: #000;
}

.grid img {
    display: block;
    width: 100%;
    height: 100%;
    aspect-ratio: 3 / 4;
    object-fit: cover;
    filter: grayscale(1) contrast(1.12) brightness(0.86);
    transition: filter 400ms ease, transform 600ms ease;
}

.grid figure:hover img {
    filter: grayscale(1) contrast(1.18) brightness(1);
    transform: scale(1.03);
}

/* Vignette instead of a frame. */
.grid figure::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    box-shadow: inset 0 0 70px 26px rgba(0, 0, 0, 0.8);
}

.grid figcaption {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 2;
    padding: 30px 18px 14px;
    font-family: var(--display);
    text-transform: uppercase;
    letter-spacing: 0.2em;
    font-size: 0.72rem;
    color: #c9c9c9;
    background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.85));
}

/* Spare utility: put class="wide" on a figure to run it across
   both columns as a landscape hero. */
.grid figure.wide {
    grid-column: span 2;
}

.grid figure.wide img {
    aspect-ratio: 16 / 10;
}

@media (max-width: 700px) {
    .grid {
        grid-template-columns: 1fr;
    }

    .grid figure.wide {
        grid-column: span 1;
    }

    .grid figure.wide img {
        aspect-ratio: 3 / 4;
    }
}


/* =========================================================
   FRAMED PLATES

   All three frame PNGs are corner ornaments, not rectangles:
   the decoration occupies two adjoining edges and sweeps well
   inward, leaving one open diagonal corner. Frame2's foliage
   reaches 40% across, so nothing can sit in the middle - the
   content goes in the open corner, and the clear area of each
   was measured off the alpha channel rather than guessed.

   The plate carries the PNG's own ratio, so the contents can
   never slide off the art at any width.
   ========================================================= */

.plate {
    position: relative;
    width: min(620px, 100%);
    margin: 0 auto;
}

.plate__frame {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: contain;
    pointer-events: none;
}

/* Content sits in the open corner. Each plate sets its own.
   Centred vertically so short blocks do not hang from the top
   of a tall opening. */
.plate__body {
    position: absolute;
    display: flex;
    flex-direction: column;
    justify-content: center;
    overflow: auto;
    text-align: left;
}

.plate__body h2 {
    text-align: left;
    font-size: clamp(1.1rem, 2.4vw, 1.5rem);
    margin-bottom: 0.9em;
}

.plate__body p {
    font-size: 0.9em;
    line-height: 1.7;
}


/* --- Archive: illuminated corner, open at the bottom left.
       Measured clear area is 0-59% across, 29-100% down. --- */

.plate--archive {
    aspect-ratio: 1024 / 1536;
}

.plate--archive .plate__frame {
    filter: grayscale(1) contrast(1.15) brightness(1.06);
    opacity: 0.92;
}

.plate--archive .plate__body {
    left: 4%;
    top: 36%;
    width: 50%;
    height: 58%;
}


/* --- About: art nouveau corner, open at the top left.
       Measured clear area is 0-59% across, 0-69% down. --- */

.plate--portrait {
    aspect-ratio: 1024 / 1536;
    width: min(420px, 100%);
    margin: 0;
}

.plate--portrait .plate__frame {
    filter: grayscale(1) contrast(1.18) brightness(1.08);
    opacity: 0.95;
}

/* The painting is a rectangle, and a rectangle is an edge. The
   mask dissolves all four sides so the figure surfaces out of
   the dark instead of sitting in a box - two gradients
   intersected, so the falloff is stated outright rather than
   whatever a radial happens to reach. */
.plate--portrait .plate__figure {
    position: absolute;
    left: 3%;
    top: 3%;
    width: 52%;
    height: 63%;
    margin: 0;
    overflow: hidden;
    background: #000;
    -webkit-mask-image:
        linear-gradient(to right,  rgba(0,0,0,0) 0%, #000 24%, #000 88%, rgba(0,0,0,0) 100%),
        linear-gradient(to bottom, rgba(0,0,0,0) 0%, #000 20%, #000 90%, rgba(0,0,0,0) 100%);
    -webkit-mask-composite: source-in;
    mask-image:
        linear-gradient(to right,  rgba(0,0,0,0) 0%, #000 24%, #000 88%, rgba(0,0,0,0) 100%),
        linear-gradient(to bottom, rgba(0,0,0,0) 0%, #000 20%, #000 90%, rgba(0,0,0,0) 100%);
    mask-composite: intersect;
}

.plate--portrait .plate__figure img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: 50% 12%;
    filter: grayscale(1) contrast(1.15) brightness(0.92);
}

.plate--portrait .plate__figure::after {
    content: "";
    position: absolute;
    inset: 0;
    box-shadow: inset 0 0 45px 18px rgba(0, 0, 0, 0.8);
}


/* --- About layout: framed portrait beside the words. --- */

.portrait-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: clamp(20px, 4vw, 48px);
}

.portrait-row .measure {
    flex: 1 1 320px;
    max-width: 46ch;
    text-align: left;
    margin: 0;
}

.portrait-row .measure h2 {
    text-align: left;
}


/* ---------------------------------------------------------
   FOOTER
   --------------------------------------------------------- */

footer {
    position: relative;
    text-align: center;
    padding: clamp(60px, 10vw, 110px) var(--gutter) clamp(120px, 16vw, 200px);
    color: #4e4e4e;
    font-size: 0.8rem;
    letter-spacing: 0.2em;
    line-height: 2.4;
}

/* Positioned so the words stay above the surf behind them. */
.footer-inner {
    position: relative;
    z-index: 5;
}

footer .label {
    display: block;
    font-size: 1.1rem;
    letter-spacing: 0.5em;
    text-indent: 0.5em;
    color: #7a7a7a;
    margin-bottom: 0.8em;
}


/* =========================================================
   STAGES

   Free-placed panels built in editor.html. Every item inside a
   stage is positioned as a percentage of the stage box, and the
   box carries a fixed ratio - so a layout you drag together at
   desktop size stays welded together at every width. It is the
   same trick the nameplate uses, generalised.

   Text is sized in cqw (percent of the stage's own width) with
   a pixel floor, so it scales with the artwork instead of
   sliding out of position, but never shrinks past legibility.
   ========================================================= */

.stage {
    position: relative;
    width: 100%;
    max-width: 1000px;
    margin: clamp(60px, 9vw, 120px) auto;
    container-type: inline-size;
}

/* Wider working areas. The full-window one breaks out of the
   site's centred column by pulling itself back to the viewport
   edges - body already hides sideways overflow, so the extra
   fraction of a pixel never raises a scrollbar. */
.stage--wide { max-width: 1240px; }

.stage--full {
    max-width: none;
    width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
}

.stage__item {
    position: absolute;
    margin: 0;
    --o: 1;
}

.stage__item img {
    display: block;
    width: 100%;
    height: auto;
}

/* =========================================================
   PAGE BACKGROUND

   Its own fixed layer rather than a background on <body>, so it
   sits over the page's dark wash without replacing it, and can
   be dimmed or greyed on its own. Behind the drifting ship and
   castle, which are at -5.
   ========================================================= */

.page-bg {
    position: fixed;
    inset: 0;
    z-index: -6;
    background-position: center center;
    background-repeat: no-repeat;
    background-size: cover;
    pointer-events: none;
}

.page-bg--contain { background-size: contain; }

.page-bg--tile {
    background-repeat: repeat;
    background-size: auto;
}


/* =========================================================
   STACKED PANELS

   A normal panel places everything by coordinate. A stacked one
   lets its contents fall down the page in order instead, which
   is what a blog wants - entries that grow as they open cannot
   live at fixed coordinates without landing on each other.
   ========================================================= */

.stage--flow {
    aspect-ratio: auto !important;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: clamp(20px, 3vw, 38px);
    padding: clamp(16px, 3vw, 36px) 0;
}

.stage--flow .stage__item {
    position: relative;
}


/* =========================================================
   MUSIC PLAYER

   Built out of a hidden <audio> and ordinary controls rather
   than the browser's own player, so it can be made to match the
   rest of the page. The two sliders are range inputs on purpose:
   dragging, clicking partway along and arrow keys all come free
   and work the same for everyone.
   ========================================================= */

.player {
    background: #161616;
    border: 2px solid;
    border-color: #7a7a7a #0c0c0c #0c0c0c #7a7a7a;
    box-shadow: inset 1px 1px 0 #303030, inset -1px -1px 0 #050505;
    padding: 9px 11px;
    display: flex;
    flex-direction: column;
    gap: 7px;
    justify-content: center;
}

.player__row {
    display: flex;
    align-items: center;
    gap: 9px;
}

.player__play {
    flex: none;
    width: 2.4em;
    height: 2.1em;
    font-size: 0.85em;
    line-height: 1;
    color: #dcdcdc;
    background: #2b2b2b;
    border: 2px solid;
    border-color: #8f8f8f #0a0a0a #0a0a0a #8f8f8f;
    cursor: pointer;
}

.player__play:hover { background: #383838; color: #fff; }
.player__play:active { border-color: #0a0a0a #8f8f8f #8f8f8f #0a0a0a; }

.player__name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--display);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    font-size: 0.68em;
    color: #b4b4b4;
}

.player__time {
    flex: none;
    font-size: 0.62em;
    letter-spacing: 0.08em;
    color: var(--ink-dim);
    font-variant-numeric: tabular-nums;
}

.player__seek { flex: 1; }
.player__vol  { flex: none; width: 5.5em; }

/* Sliders, sunk into the panel with a raised grip. */
.player input[type="range"] {
    -webkit-appearance: none;
    appearance: none;
    height: 1.1em;
    min-height: 14px;
    background: none;
    cursor: pointer;
}

.player input[type="range"]::-webkit-slider-runnable-track {
    height: 6px;
    background: #0b0b0b;
    border: 1px solid;
    border-color: #090909 #6e6e6e #6e6e6e #090909;
}

.player input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 11px;
    height: 17px;
    margin-top: -7px;
    background: #c4c4c4;
    border: 1px solid;
    border-color: #efefef #0c0c0c #0c0c0c #efefef;
}

.player input[type="range"]::-moz-range-track {
    height: 6px;
    background: #0b0b0b;
    border: 1px solid #4a4a4a;
}

.player input[type="range"]::-moz-range-thumb {
    width: 9px;
    height: 15px;
    border-radius: 0;
    background: #c4c4c4;
    border: 1px solid #efefef;
}

.player input[type="range"]:focus-visible { outline: 1px solid #d8d8d8; }

.player audio { display: none; }


/* =========================================================
   EMBEDS

   Someone else's page running inside yours - a guestbook, a
   player, a map. The item becomes a column so the frame can take
   all the room going and the escape link sits under it.
   ========================================================= */

.stage__item.has-embed {
    display: flex;
    flex-direction: column;
}

.stage__embed {
    flex: 1;
    width: 100%;
    border: 0;
    background: #0d0d0d;
}

/* Browsers and extensions do block third-party frames. When that
   happens the visitor gets a way through rather than a hole. */
.stage__escape {
    display: block;
    margin-top: 7px;
    text-align: center;
    font-size: 0.62em;
    letter-spacing: 0.16em;
    color: var(--ink-dim);
}


/* =========================================================
   WINDOW FRAMES

   The old beveled look, done in greys so it belongs on a black
   and white page: two borders of opposite lightness fake a lit
   edge and a shadowed one. These are the borders you asked for.
   ========================================================= */

.win {
    background: #161616;
    border: 2px solid;
    border-color: #7a7a7a #0c0c0c #0c0c0c #7a7a7a;
    box-shadow: inset 1px 1px 0 #303030, inset -1px -1px 0 #050505;
}

.win__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 5px 6px 5px 9px;
    background: linear-gradient(90deg, #d2d2d2, #6b6b6b);
    color: #080808;
    font-family: var(--display);
    text-transform: uppercase;
    letter-spacing: 0.16em;
    font-size: 0.72em;
    line-height: 1.4;
}

/* The little box buttons that never did anything. */
.win__keys {
    display: flex;
    gap: 3px;
    flex: none;
}

.win__keys i {
    width: 13px;
    height: 12px;
    background: #c4c4c4;
    border: 1px solid;
    border-color: #efefef #0c0c0c #0c0c0c #efefef;
}

.win__body {
    padding: clamp(12px, 2vw, 20px);
}

.win__teaser {
    margin: 0 0 1em;
}

.win__btn {
    display: inline-block;
    font-family: var(--display);
    text-transform: uppercase;
    letter-spacing: 0.18em;
    font-size: 0.78em;
    color: #dcdcdc;
    background: #2b2b2b;
    border: 2px solid;
    border-color: #8f8f8f #0a0a0a #0a0a0a #8f8f8f;
    padding: 6px 20px;
    cursor: pointer;
    list-style: none;
}

.win__btn::-webkit-details-marker { display: none; }
.win__btn::marker { content: ""; }

.win__btn:hover { background: #383838; color: #fff; }

details[open] > .win__btn,
.win__btn:active {
    border-color: #0a0a0a #8f8f8f #8f8f8f #0a0a0a;
}

.win__text {
    margin-top: 1.1em;
    white-space: pre-wrap;
}

/* =========================================================
   THE READER

   An entry opens in a window over the page rather than growing
   in place. Nothing below it moves, the page never gets taller,
   and long writing scrolls inside the window instead of turning
   the whole site into a scroll. It is also the one place type
   is set at a plain readable size rather than scaled off a
   panel, because this is where the reading actually happens.
   ========================================================= */

.reader {
    width: min(680px, 92vw);
    max-height: 82vh;
    padding: 0;
    background: #161616;
    color: var(--ink);
    font-family: var(--body);
    font-size: clamp(14px, 1rem, 18px);
    line-height: 1.8;
    border: 2px solid;
    border-color: #7a7a7a #0c0c0c #0c0c0c #7a7a7a;
    box-shadow:
        inset 1px 1px 0 #303030,
        inset -1px -1px 0 #050505,
        0 0 70px rgba(0, 0, 0, 0.85);
    overflow: hidden;
}

/* A dialog is display:none until it is open, so the layout only
   applies then - setting it on the base rule shows it always. */
.reader[open] {
    display: flex;
    flex-direction: column;
}

.reader::backdrop { background: rgba(0, 0, 0, 0.86); }

.reader__bar {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 5px 6px 5px 11px;
    background: linear-gradient(90deg, #d2d2d2, #6b6b6b);
    color: #080808;
    font-family: var(--display);
    text-transform: uppercase;
    letter-spacing: 0.16em;
    font-size: 0.72em;
}

.reader__shut {
    flex: none;
    width: 22px;
    height: 19px;
    line-height: 1;
    font-size: 11px;
    color: #0a0a0a;
    background: #c4c4c4;
    border: 1px solid;
    border-color: #efefef #0c0c0c #0c0c0c #efefef;
    cursor: pointer;
}

.reader__shut:active { border-color: #0c0c0c #efefef #efefef #0c0c0c; }

.reader__body {
    overflow-y: auto;
    padding: 20px 22px 24px;
}


/* The cursor that runs along behind the typing. */
.win__text.is-typing::after {
    content: "\258B";
    margin-left: 1px;
    animation: lp-blink 1s steps(1) infinite;
}

@keyframes lp-blink {
    0%, 50% { opacity: 1; }
    50.01%, 100% { opacity: 0; }
}


/* =========================================================
   ENLARGING A PHOTOGRAPH
   ========================================================= */

.can-zoom { cursor: zoom-in; }

.lightbox {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: none;
    place-items: center;
    padding: 4vmin;
    background: rgba(0, 0, 0, 0.93);
    cursor: zoom-out;
}

.lightbox.is-open { display: grid; }

.lightbox img {
    max-width: 100%;
    max-height: 92vh;
    width: auto;
    height: auto;
    object-fit: contain;
}

.lightbox__shut {
    position: absolute;
    top: 3vmin;
    right: 3vmin;
    font-family: var(--display);
    text-transform: uppercase;
    letter-spacing: 0.2em;
    font-size: 0.72rem;
    color: #dcdcdc;
    background: #2b2b2b;
    border: 2px solid;
    border-color: #8f8f8f #0a0a0a #0a0a0a #8f8f8f;
    padding: 6px 16px;
    cursor: pointer;
}


/* =========================================================
   ROLLOVERS

   An item can hold a second picture that takes over while the
   pointer is on it. Both are stacked in the same box: the first
   one is in normal flow and sets the height, the second is laid
   over it from the centre out, so a replacement drawn on a
   bigger canvas spills past the edges instead of being squashed
   into them - which is what makes a thing appear to pop out.

   Nothing is hidden by default, so with the stylesheet missing
   or the swap absent you still see the original picture.
   ========================================================= */

.stage__swap {
    position: absolute;
    left: 50%;
    top: 50%;
    translate: -50% -50%;
    height: auto;
    opacity: 0;
    pointer-events: none;
}

.stage__base,
.stage__swap {
    transition: opacity 260ms ease;
}

/* Guarded on real hover. A touch screen has no pointer resting
   anywhere, and without this the swapped state sticks after a
   tap and will not clear. */
@media (hover: hover) {

    .stage__item.has-swap:hover .stage__base,
    a.stage__item.has-swap:focus-visible .stage__base {
        opacity: 0;
    }

    .stage__item.has-swap:hover .stage__swap,
    a.stage__item.has-swap:focus-visible .stage__swap {
        opacity: 1;
    }
}

@media (prefers-reduced-motion: reduce) {
    .stage__base,
    .stage__swap { transition: none; }
}


/* An item that is also a link. The site styles links as small
   tracked capitals, which would wreck a picture or a heading,
   so a linked item keeps its own settings and inherits nothing
   from that. */
a.stage__item {
    display: block;
    font: inherit;
    color: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    text-decoration: none;
    cursor: pointer;
    transition: filter 220ms ease;
}

/* Lit rather than outlined, so a clickable item announces itself
   without a border. Brightness is used because the colour toggle
   writes filter straight onto the image, and this sits on the
   link around it instead of fighting for the same property. */
a.stage__item:hover {
    filter: brightness(1.25);
}

a.stage__item:focus-visible {
    outline: none;
    filter: brightness(1.6) drop-shadow(0 0 12px rgba(255, 255, 255, 0.55));
}

.stage__text {
    font-family: var(--body);
    line-height: 1.75;
    color: var(--ink);
}

/* div is in here because that is what a browser inserts when you
   press Enter inside an editable box. */
.stage__text p,
.stage__text div {
    margin: 0 0 0.9em;
}

.stage__text p:last-child,
.stage__text div:last-child {
    margin-bottom: 0;
}

/* The display face, for headings and labels inside a stage. */
.stage__text.is-display {
    font-family: var(--display);
    text-transform: uppercase;
    letter-spacing: 0.2em;
    line-height: 1.2;
    color: var(--ink-bright);
}

/* Type bent along an arc, drawn rather than laid out. The glyphs
   rotate to follow the path, the same way the words ride the
   ribbon on the front page. The SVG's viewBox is a fixed 1000
   wide, so every measurement inside it is a proportion of the
   item's own width and the curve keeps its shape at any size. */
.stage__curve {
    display: block;
    width: 100%;
    height: auto;
    overflow: visible;
}

.stage__text.is-curved {
    line-height: 0;
}

/* Face and letter spacing are deliberately absent here. Curved
   type carries them as SVG attributes, and a rule at this level
   would beat those attributes and freeze both controls. */


/* =========================================================
   MOTION

   Two rules keep this from fighting the rest of the system.
   One: the keyframes drive the individual `translate` and
   `rotate` properties, never `transform` - so an item can be
   warped and moving at the same time instead of the animation
   wiping out the warp. Two: opacity keyframes resolve against
   --o, the item's own set opacity, so a fade lands on the
   value you chose rather than flooring it at 1.

   Nothing moves for a reader who has asked for less motion.
   ========================================================= */

@media (prefers-reduced-motion: no-preference) {

    .anim {
        animation-duration: var(--dur, 6s);
        animation-delay: var(--delay, 0s);
        animation-fill-mode: both;
    }

    .anim--fade { animation-name: lp-fade; }
    .anim--rise { animation-name: lp-rise; }

    .anim--drift,
    .anim--sway,
    .anim--breathe,
    .anim--ghost {
        animation-iteration-count: infinite;
        animation-direction: alternate;
        animation-timing-function: ease-in-out;
    }

    .anim--drift   { animation-name: lp-drift; }
    .anim--sway    { animation-name: lp-sway; }
    .anim--breathe { animation-name: lp-breathe; }
    .anim--ghost   { animation-name: lp-ghost; }

    .anim--flicker {
        animation-name: lp-flicker;
        animation-iteration-count: infinite;
    }
}

@keyframes lp-fade {
    from { opacity: 0; }
    to   { opacity: var(--o, 1); }
}

@keyframes lp-rise {
    from { opacity: 0; translate: 0 22px; }
    to   { opacity: var(--o, 1); translate: 0 0; }
}

@keyframes lp-drift {
    from { translate: 0 0; }
    to   { translate: 0 -16px; }
}

@keyframes lp-sway {
    from { rotate: -1.4deg; }
    to   { rotate: 1.4deg; }
}

@keyframes lp-breathe {
    from { opacity: var(--o, 1); }
    to   { opacity: calc(var(--o, 1) * 0.35); }
}

/* A failing bulb. Irregular on purpose - evenly spaced flicker
   reads as a loop rather than as a fault. */
@keyframes lp-flicker {
    0%, 41%, 43%, 46%, 77%, 80%, 100% { opacity: var(--o, 1); }
    42% { opacity: calc(var(--o, 1) * 0.25); }
    45% { opacity: calc(var(--o, 1) * 0.45); }
    78% { opacity: calc(var(--o, 1) * 0.15); }
}

/* Slow bloom of light across engraved type. */
@keyframes lp-ghost {
    from { filter: brightness(0.72); }
    to   { filter: brightness(1.35); }
}


/* Opt-in edge feather, for art that ends on a straight crop
   rather than its own outline. Same treatment as .art--tree
   and friends above. */
.is-feathered {
    -webkit-mask-image:
        linear-gradient(to right,  rgba(0,0,0,0) 0%, #000 12%, #000 88%, rgba(0,0,0,0) 100%),
        linear-gradient(to bottom, rgba(0,0,0,0) 0%, #000 12%, #000 88%, rgba(0,0,0,0) 100%);
    -webkit-mask-composite: source-in;
    mask-image:
        linear-gradient(to right,  rgba(0,0,0,0) 0%, #000 12%, #000 88%, rgba(0,0,0,0) 100%),
        linear-gradient(to bottom, rgba(0,0,0,0) 0%, #000 12%, #000 88%, rgba(0,0,0,0) 100%);
    mask-composite: intersect;
}


/* ---------------------------------------------------------
   NARROW SCREENS

   A frame is a fixed-ratio ornament: its opening shrinks with
   the screen while the paragraph inside it does not. Past this
   width the archive text would be a 140px column scrolling
   inside a picture. So the frame steps back to being a corner
   flourish and the words go back to being ordinary flowing
   text. The portrait plate is left alone - it holds a picture,
   which is happy to just get smaller.
   --------------------------------------------------------- */

@media (max-width: 760px) {

    .plate--archive {
        aspect-ratio: auto;
        width: 100%;
    }

    .plate--archive .plate__frame {
        inset: -5% -12% auto auto;
        width: 60%;
        height: auto;
        opacity: 0.36;
    }

    .plate--archive .plate__body {
        position: static;
        width: auto;
        height: auto;
        overflow: visible;
    }

    .plate--portrait {
        margin-inline: auto;
    }

}


/* ---------------------------------------------------------
   MOTION
   --------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    * { transition: none !important; }
}


/* =========================================================
   OVERRIDES

   Last in the file on purpose. The greyscale gate near the top
   is deliberately hard to escape, and several section rules
   (.grid img, .art--*) restate it at the same weight - so the
   escape hatch has to come after all of them to win on order.
   ========================================================= */

/* One image allowed to keep its colour. A single coloured thing
   in a black and white page reads as deliberate; that is the
   whole reason this exists. */
img.in-colour,
.in-colour img {
    filter: none;
}
