/* ==========================================================================
   The window frame and its body types.
   ========================================================================== */

.hxp-windows {
    position: absolute;
    top: 0;
    bottom: var(--taskbar-h);
    left: var(--canvas-gutter);
    right: var(--canvas-gutter);
    z-index: 10;

    /* This layer spans the whole desktop and sits above the icons, so left
       solid it swallows every click aimed at them. Only the windows
       themselves should take the pointer. */
    pointer-events: none;
}

.hxp-win {
    pointer-events: auto;
}

/* --------------------------------------------------------------------------
   Frame
   -------------------------------------------------------------------------- */

.hxp-win {
    position: absolute;
    /* Placement is a fraction of the free space, so a window authored flush
       against an edge stays flush against it at any canvas width. */
    left: calc(var(--fx) * (100% - var(--w) * var(--u)));
    top: calc(var(--fy) * (100% - var(--h) * var(--u)));
    width: calc(var(--w) * var(--u));
    height: calc(var(--h) * var(--u));
    z-index: var(--z);

    display: flex;
    flex-direction: column;

    /* The window sizes its own contents. Everything inside lays itself out
       from this width rather than from whether the window happens to be
       maximised - so dragging a widget wide by hand gives you the expanded
       design, separators, padding and alignment included. Safe to contain:
       the width is set explicitly, never derived from the contents. */
    container-type: inline-size;
    container-name: win;

    background: var(--glass);
    border: 1px solid var(--glass-edge);
    border-radius: var(--win-radius);
    backdrop-filter: blur(12px);

    /* Two slots rather than one shadow: the lift the frame always casts, and a
       glow only the active window carries. Kept apart so dragging the active
       window keeps both instead of one state overwriting the other. */
    --lift: 0 18px 50px -20px rgba(0, 0, 0, 0.6);
    --glow: 0 0 0 0 rgba(0, 0, 0, 0);
    box-shadow: var(--glow), var(--lift);
    overflow: hidden;

    /* Drag writes these; they stay at 0 the rest of the time. */
    translate: calc(var(--dx, 0) * 1px) calc(var(--dy, 0) * 1px);

    opacity: 1;
    visibility: visible;

    /* Expanding is the same widget growing, not a different window opening. */
    transition:
        left 0.3s var(--ease),
        top 0.3s var(--ease),
        width 0.3s var(--ease),
        height 0.3s var(--ease),
        opacity 0.22s var(--ease),
        visibility 0s;
}

/* A gesture must track the pointer exactly, with nothing easing behind it. */
.hxp-win.is-dragging,
.hxp-win.is-resizing {
    transition: none;
}

/* The real per-frame cost of dragging a glass window is its own backdrop.
   `backdrop-filter` samples everything painted behind the element, so a window
   standing still blurs once and is cached - but a moving one has a different
   backdrop every frame and has to re-blur the whole face, at device pixels, on
   every one. That is what makes a drag feel rubbery: the pointer is ahead and
   the blur is catching up.

   The face is 85% opaque (--glass), so the blur contributes about a seventh of
   each pixel - imperceptible against a photo wallpaper, and completely
   invisible on something that is moving. Dropping it for the length of the
   gesture leaves nothing to recompute, and `will-change` hands the movement to
   the compositor so the frame is a layer shift rather than a repaint.

   Both individual and combined transform properties are named: the movement is
   written to `translate`, but engines promote most reliably on `transform`. */
.hxp-win.is-dragging,
.hxp-win.is-resizing {
    backdrop-filter: none;
    will-change: transform, translate;
}

@media (prefers-reduced-motion: reduce) {
    .hxp-win {
        transition: none;
    }
}

/* Putting a window away fades it. display:none cannot be transitioned, so a
   minimised window would vanish and reappear with a pop - which is what made
   the desktop look like it was jumping around during navigation. */
.hxp-win[data-state="min"],
.hxp-win[data-state="closed"] {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition:
        opacity 0.22s var(--ease),
        visibility 0s linear 0.22s;
}

.hxp-win.is-dragging {
    cursor: grabbing;
    --lift: 0 30px 70px -20px rgba(0, 0, 0, 0.7);
    user-select: none;
}

/* The active window — the one whose taskbar tab is lit. A thin Harry-yellow
   edge and a soft glow: enough to pick a widget out of nine overlapping ones
   the moment its tab is clicked, without turning the desktop into a light
   show. Set from the taskbar's own active-window pass, so it can never point
   at a different window than the lit tab does. */
.hxp-win.is-active {
    border-color: rgba(237, 255, 0, 0.55);
    --glow:
        0 0 0 1px rgba(237, 255, 0, 0.4),
        0 0 22px -2px rgba(237, 255, 0, 0.32);
}

/* A maximised window covers the desktop, so there is nothing to pick it out
   from - the ring would just be a yellow line around the whole screen. */
.hxp-win.is-active[data-state="max"] {
    border-color: var(--glass-edge);
    --glow: 0 0 0 0 rgba(0, 0, 0, 0);
}

.hxp-win[data-state="max"] {
    left: calc(9 * var(--u));
    top: calc(93 * var(--u));
    width: calc(100% - 18 * var(--u));
    height: calc(100% - 102 * var(--u));

    /* Deliberately NOT a fixed z-index. Pinning it at 50 put an expanded
       window *underneath* any widget the visitor had raised past that, so
       opening one could leave it buried. Whatever opens is focused, and
       focusing is what puts a window on top. */
    z-index: var(--z);
}

/* --------------------------------------------------------------------------
   Title bar
   -------------------------------------------------------------------------- */

.hxp-win__bar {
    position: relative;   /* for the separator below */
    flex-shrink: 0;
    height: calc(var(--win-bar-h) * var(--u));
    padding-inline: calc(var(--win-pad) * var(--u));

    display: flex;
    align-items: center;
    gap: calc(12 * var(--u));

    cursor: grab;
    touch-action: none;
}

/* The separator stops where the content does.

   As a border on the bar it ran the full width of the frame, out past the
   padding that the title above it and everything below it are set to - so the
   one line in the window that exists to divide the content was the one thing
   ignoring the column it was dividing. Drawn as a pseudo-element instead, inset
   by the same --win-pad every other element reads, so it stays aligned through
   the narrow widgets (13 units), the gimmick frames (9) and the mobile stack
   (16) without any of them restating it. */
.hxp-win__bar::after {
    content: "";
    position: absolute;
    left: calc(var(--win-pad) * var(--u));
    right: calc(var(--win-pad) * var(--u));
    bottom: 0;
    height: 1px;
    background: var(--glass-rule);
    pointer-events: none;   /* the bar is the drag handle */
}

.hxp-win[data-state="max"] .hxp-win__bar {
    cursor: default;
}

.hxp-win__title {
    margin: 0;
    font-size: calc(var(--t-title) * var(--u));
    font-weight: 600;
    line-height: 1;
    text-transform: capitalize;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The title is a drag surface as well as a link, so the browser must not start
   its own drag of the URL when it is pulled. draggable="false" in the markup
   covers most engines; this covers WebKit. */
.hxp-win__title-link {
    text-decoration: none;
    color: inherit;
    -webkit-user-drag: none;
}

/* No underline on hover. The title is a handle you grab the window by as much
   as a link you click, and an underline appearing under the pointer reads as
   "this is a hyperlink" - which invites exactly the wrong gesture. */
.hxp-win__title-link:hover,
.hxp-win__title-link:focus {
    text-decoration: none;
}

.hxp-win__tools {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: calc(10 * var(--u));
    flex-shrink: 0;
}

/* --- progress readout --------------------------------------------------- */

/* The readout belongs to the scattered window. Maximised, every card is on
   screen at once, so counting them says nothing - the design leaves it off. */
.hxp-win__progress {
    display: flex;
    align-items: center;
    gap: calc(10 * var(--u));
}

.hxp-win__progress-label {
    font-size: calc(var(--t-micro) * var(--u));
    color: var(--ink-muted);
    white-space: nowrap;
}

.hxp-win__progress-pill {
    position: relative;
    display: grid;
    place-items: center;
    width: calc(66 * var(--u));
    height: calc(13 * var(--u));
    border-radius: 100px;
    background: linear-gradient(90deg, var(--pill-from), var(--pill-to));
    overflow: hidden;
}

.hxp-win__progress-fill {
    position: absolute;
    inset: 0 auto 0 0;
    width: var(--fill, 0%);
    background: rgba(249, 243, 83, 0.32);
    transition: width 0.12s linear;
}

.hxp-win__progress-num {
    position: relative;
    font-size: calc(var(--t-micro) * var(--u));
    font-weight: 300;
    line-height: 1;
    white-space: nowrap;
}

/* Wide enough to be the expanded design, and the readout is widget chrome.
   This has to sit after the rules above: @container adds no specificity, so a
   later plain rule would simply win. */
@container win (min-width: 740px) {
    .hxp-win__progress {
        display: none;
    }
}

/* --- traffic lights ----------------------------------------------------- */

/* Only the maximised pages carry these in the design. On the scattered desktop
   a row of macOS dots on an XP window reads as the wrong operating system. */
.hxp-win:not([data-state="max"]) .hxp-lights {
    display: none;
}

.hxp-lights {
    display: flex;
    align-items: center;
    gap: calc(8 * var(--u));
}

.hxp-light {
    width: calc(13 * var(--u));
    height: calc(13 * var(--u));
    border-radius: 50%;
    box-shadow: inset 0 -1px 2px rgba(0, 0, 0, 0.28);
    transition: filter 0.15s var(--ease);
}

.hxp-light:hover {
    filter: brightness(1.2);
}

.hxp-light--close { background: var(--light-close); }
.hxp-light--min   { background: var(--light-min); }
.hxp-light--max   { background: var(--light-max); }

/* --------------------------------------------------------------------------
   Body

   Text is never scaled to fit - it stays at the size it was designed at and
   scrolls when there is more of it than the frame holds. Structure still fits:
   grids and media divide the frame between them.
   -------------------------------------------------------------------------- */

.hxp-win__body {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    padding: calc(var(--win-pad) * var(--u));
    overflow: hidden;
}



.hxp-win__scroll {
    flex: 1 1 auto;
    min-height: 0;
    /*display: flex;*/
    flex-direction: column;
    overflow-y: auto;
    overflow-x: hidden;
    overscroll-behavior: contain;

    /* The native bar is hidden in favour of the design's own indicator below.
       On macOS the native one is an overlay: it takes no space and fades out,
       leaving the cut edge as the only cue that there is more to read. */
    scrollbar-width: none;
}

.hxp-win__scroll::-webkit-scrollbar {
    display: none;
}

.hxp-win__scroll > * {
    flex: 1 1 auto;
    min-height: 0;
}

/* --- scroll indicator ---------------------------------------------------
   The slim yellow bar from the design. Always visible while the window has
   more to show, unlike the platform's overlay scrollbar.
   ---------------------------------------------------------------------- */

.hxp-scrollbar {
    position: absolute;
    top: calc(var(--win-pad) * var(--u));
    bottom: calc(var(--win-pad) * var(--u));
    right: calc(var(--win-pad) * var(--u));
    width: calc(8 * var(--u));
    border-radius: 100px;
    background: var(--glass-inset);
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.2s var(--ease);
}

.hxp-win__body.is-scrollable .hxp-scrollbar {
    opacity: 1;
}

.hxp-win__body.is-scrollable .hxp-win__scroll {
    padding-right: calc(20 * var(--u));
}

.hxp-scrollbar__thumb {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    height: 20%;
    border-radius: 100px;
    background: var(--harry-yellow);
    box-shadow: 0 calc(4 * var(--u)) calc(4 * var(--u)) rgba(0, 0, 0, 0.25);
}

/* --------------------------------------------------------------------------
   Cards

   A window with a segmented bar slides through its cards. Snapping is
   "proximity", not "mandatory", so a card with more text than the frame holds
   can still be read through rather than fighting the scroll back to its top.
   -------------------------------------------------------------------------- */

.hxp-cards {
    display: flex;
    flex-direction: column;
    min-height: 0;
}

.hxp-win--slides[data-state="open"] .hxp-win__scroll {
    scroll-snap-type: y proximity;
    scroll-behavior: smooth;
}

.hxp-win[data-state="open"] .hxp-card-slide,
.hxp-win[data-state="open"] .hxp-project,
.hxp-win[data-state="open"] .hxp-card {
    scroll-snap-align: start;
}

@media (prefers-reduced-motion: reduce) {
    .hxp-win__scroll {
        scroll-behavior: auto;
    }
}

/* --- resize grip -------------------------------------------------------- */

.hxp-win__resize {
    position: absolute;
    right: 0;
    bottom: 0;
    width: calc(18 * var(--u));
    height: calc(18 * var(--u));
    display: grid;
    place-items: center;
    color: var(--ink);
    cursor: nwse-resize;
    touch-action: none;
    opacity: 0.5;
    transition: opacity 0.15s var(--ease);
}

.hxp-win__resize:hover,
.hxp-win__resize:focus-visible,
.hxp-win.is-resizing .hxp-win__resize {
    opacity: 1;
}

.hxp-win__resize svg {
    width: calc(11 * var(--u));
    height: calc(11 * var(--u));
}

/* A maximised window fills the frame; there is nothing to resize. */
.hxp-win[data-state="max"] .hxp-win__resize {
    display: none;
}

.hxp-win.is-resizing {
    user-select: none;
}

/* --- footer segments ---------------------------------------------------- */

.hxp-win__segments {
    flex-shrink: 0;
    display: flex;
    gap: calc(12 * var(--u));
    padding: 0 calc(var(--win-pad) * var(--u)) calc(var(--win-pad) * var(--u));
}

.hxp-seg {
    flex: 1 1 0;
    height: calc(8 * var(--u));
    border-radius: 100px;
    background: var(--glass-inset);
    transition: background 0.2s var(--ease);
}

@container win (min-width: 740px) {
    .hxp-win__segments { display: none; }
}

.hxp-seg.is-on {
    background: var(--harry-yellow);
    box-shadow: 0 4px 4px rgba(0, 0, 0, 0.25);
}

/* ==========================================================================
   Shared body primitives
   ========================================================================== */

.hxp-eyebrow {
    margin: 0 0 calc(4 * var(--u));
    font-size: calc(var(--t-eyebrow) * var(--u));
    font-weight: 400;
    line-height: 1.2;
    color: var(--ink-muted);
}

.hxp-lead {
    margin: 0 0 calc(14 * var(--u));
    font-size: calc(var(--t-lead) * var(--u));
    font-weight: 600;
    line-height: 1.2;
}

.hxp-prose {
    font-size: calc(var(--t-body) * var(--u));
    line-height: 1.42;
}

.hxp-prose > :first-child { margin-top: 0; }
.hxp-prose > :last-child  { margin-bottom: 0; }
.hxp-prose p { margin: 0 0 1em; }

.hxp-empty {
    margin: 0;
    color: var(--ink-muted);
    font-size: calc(var(--t-body) * var(--u));
}

/* --- buttons ------------------------------------------------------------ */

.hxp-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 100px;
    text-decoration: none;
    white-space: nowrap;
    transition: filter 0.15s var(--ease);
}

.hxp-btn:hover {
    filter: brightness(1.1);
}

.hxp-btn--pill {
    height: calc(19 * var(--u));
    padding-inline: calc(12 * var(--u));
    background: var(--harry-yellow);
    color: var(--mono-black);
    font-size: calc(var(--t-micro) * var(--u));
    font-weight: 600;
}

.hxp-btn--solid {
    height: calc(19 * var(--u));
    padding-inline: calc(16 * var(--u));
    background: var(--harry-yellow);
    color: var(--mono-black);
    font-size: calc(11 * var(--u));
    font-weight: 600;
}

/* --- progress bars ------------------------------------------------------ */

.hxp-bar {
    display: block;
    height: calc(8 * var(--u));
    border-radius: 100px;
    background: var(--glass-inset);
    overflow: hidden;
}

.hxp-bar__fill {
    display: block;
    height: 100%;
    width: var(--fill, 0%);
    border-radius: 100px;
    background: var(--harry-yellow);
}

/* --- media facade ------------------------------------------------------- */

.hxp-media {
    position: relative;
    display: block;
    width: 100%;
    min-height: 0;
    border-radius: calc(4 * var(--u));
    overflow: hidden;
    background: rgba(255, 255, 255, 0.06);
}

/* Direct child only. The play mark is an <img> too, and this rule was
   stretching it to fill the plate with object-fit: cover. */
.hxp-media > img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.hxp-media__empty {
    display: block;
    height: 100%;
    min-height: calc(40 * var(--u));
}

.hxp-media__play {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    pointer-events: none;
}

.hxp-media__play img,
.hxp-media__play svg {
    width: calc(86 * var(--u));
    height: auto;
    filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.35));
    transition: transform 0.2s var(--ease);
}

.hxp-media--video:hover .hxp-media__play img,
.hxp-media--video:hover .hxp-media__play svg {
    transform: scale(1.06);
}

.hxp-media video,
.hxp-media iframe {
    width: 100%;
    height: 100%;
    border: 0;
    aspect-ratio: 16 / 9;
}

/* ==========================================================================
   Type: image
   ========================================================================== */

.hxp-figure {
    margin: 0;
    height: 100%;
    border-radius: calc(4 * var(--u));
    overflow: hidden;
}

.hxp-figure img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* ==========================================================================
   Type: project-list (Track Record)
   ========================================================================== */

.hxp-projects {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: calc(14 * var(--u));
    min-height: 0;
}

.hxp-project {
    display: grid;
    grid-template-columns: 1fr calc(214 * var(--u));
    gap: calc(24 * var(--u));
    align-items: stretch;
    min-height: 0;
    padding-bottom: calc(14 * var(--u));
    border-bottom: 1px solid var(--glass-rule);
}

.hxp-project:last-child {
    border-bottom: 0;
    padding-bottom: 0;
}

/* Narrow: rows keep their designed proportions and the window scrolls through
   them - the progress readout counts those rows. */
@container win (max-width: 739.98px) {
    .hxp-project {
        flex: 0 0 auto;
    }

    .hxp-project__media .hxp-media {
        aspect-ratio: 214 / 156;
        flex: 0 0 auto;
    }

    .hxp-project__text {
        min-height: 0;
    }

    .hxp-project__description {
        display: none;   /* not in the design's compact window either */
    }
}

.hxp-project__title {
    margin: 0 0 calc(12 * var(--u));
    font-size: calc(var(--t-lead) * var(--u));
    font-weight: 600;
    line-height: 1.2;
}

.hxp-project__detail-label {
    margin-top: calc(12 * var(--u));
}

.hxp-project__subtitle,
.hxp-project__description {
    margin: 0;
    font-size: calc(var(--t-body) * var(--u));
    line-height: 1.35;
}

.hxp-project__description {
    color: var(--ink-muted);
    margin-top: calc(6 * var(--u));
}

.hxp-project__media {
    display: flex;
    flex-direction: column;
    gap: calc(8 * var(--u));
    min-height: 0;
}

.hxp-project__media .hxp-media {
    flex: 1 1 auto;
    min-height: 0;
}

/* Wide: the list becomes a grid - the same grid Content uses, down to the
   column width, the gap between rows, the rule above each card and the space
   under the text. The two windows sit side by side on the desktop and open to
   the same page furniture; anything else reads as a mistake. See .hxp-grid. */
@container win (min-width: 740px) {
    .hxp-projects {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(calc(340 * var(--u)), 1fr));
        grid-auto-rows: auto;
        gap: calc(34 * var(--u)) calc(43 * var(--u));
        align-items: start;
    }

    .hxp-project {
        grid-template-columns: 1fr;
        gap: calc(6 * var(--u));
        border-top: 1px solid var(--glass-rule);
        border-bottom: 0;
        padding-top: calc(14 * var(--u));
        padding-bottom: 0;
    }

    /* The text block reads as one run, with the same space before the picture
       that a Content card leaves. */
    .hxp-project__title {
        margin-bottom: 0;
    }

    .hxp-project__detail-label {
        margin-top: calc(6 * var(--u));
    }

    .hxp-project__description {
        margin-bottom: calc(8 * var(--u));
    }

    .hxp-project__media {
        width: 100%;
    }

    .hxp-project__media .hxp-media {
        flex: 0 0 auto;
        aspect-ratio: 405 / 248;
    }
}

/* ==========================================================================
   Film player - a Content card's film, in its card or filling the window.
   See assets/js/player.js.
   ========================================================================== */

/* In its card the player takes the poster's box exactly, so the card does not
   change shape when the film starts: the picture on top, the yellow bar under
   it, nothing round the edge. */
.hxp-media.hxp-media--playing {
    background: none;
    border-radius: 0;
    overflow: visible;
}

/* The card's own project bar would sit under the player's; one bar at a time. */
.hxp-card:has(.hxp-media--playing) > .hxp-bar {
    display: none;
}

/* The collapsed card hides its bar; the player's own bar is the one to see. */
.hxp-card .hxp-vp .hxp-bar {
    display: block;
}

.hxp-vp {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    gap: calc(10 * var(--u));
}

.hxp-vp__screen {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    border-radius: calc(4 * var(--u));
    overflow: hidden;
    background: #000;
}

/* Cover: the film fills its frame to every edge, the way the poster did,
   rather than sitting in black bars. */
.hxp-media .hxp-vp video,
.hxp-vp video {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
    aspect-ratio: auto;
    object-fit: cover;
    cursor: pointer;
}

/* Paused, the glass play mark comes back over the picture. */
.hxp-vp.is-paused .hxp-vp__screen::after {
    content: "";
    position: absolute;
    inset: 0;
    background: url("../img/play.svg") center / calc(86 * var(--u)) auto no-repeat;
    filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.35));
    pointer-events: none;
}

/* Play/pause, mute and full screen, bottom right of the picture. They show
   while the pointer is over the film, while it is paused and while one has
   focus; on touch, where there is no hover, they stay. */
.hxp-vc {
    position: absolute;
    right: calc(10 * var(--u));
    bottom: calc(10 * var(--u));
    z-index: 1;
    display: flex;
    gap: calc(6 * var(--u));
    opacity: 0;
    transition: opacity 0.2s var(--ease);
}

.hxp-vp__screen:hover .hxp-vc,
.hxp-vp.is-paused .hxp-vc,
.hxp-vc:focus-within {
    opacity: 1;
}

@media (hover: none) {
    .hxp-vc { opacity: 1; }
}

.hxp-vc__btn {
    display: grid;
    place-items: center;
    width: calc(30 * var(--u));
    height: calc(30 * var(--u));
    border-radius: 50%;
    background: rgba(16, 16, 16, 0.55);
    border: 1px solid var(--glass-edge);
    backdrop-filter: blur(8px);
    color: #fff;
    transition: background 0.15s var(--ease);
}

.hxp-vc__btn:hover {
    background: rgba(16, 16, 16, 0.8);
}

.hxp-vc__btn svg {
    width: calc(14 * var(--u));
    height: calc(14 * var(--u));
}

/* The bar is the cards' progress bar; the range input over it takes the drag. */
.hxp-vp__track {
    position: relative;
    flex: 0 0 auto;
}

/* Taller than the bar to be easy to hit, and grown upwards into the gap - the
   bar sits on the card's bottom edge, and anything below it overflows the card
   and gives the window a scrollbar. */
.hxp-vp__seek {
    position: absolute;
    left: 0;
    right: 0;
    top: calc(-10 * var(--u));
    bottom: 0;
    width: 100%;
    height: auto;
    margin: 0;
    opacity: 0;
    cursor: pointer;
}

.hxp-vp__track:has(.hxp-vp__seek:focus-visible) .hxp-bar {
    outline: 2px solid var(--harry-yellow);
    outline-offset: 3px;
}

/* Filling the window. player.js grows it out of the card's poster and shrinks
   it back; at rest it is the whole of the window's body. */
.hxp-theatre {
    position: absolute;
    inset: calc(var(--win-pad) * var(--u));
    z-index: 6;
}

.hxp-theatre .hxp-vp {
    gap: calc(14 * var(--u));
}

.hxp-theatre .hxp-vc {
    right: calc(20 * var(--u));
    bottom: calc(20 * var(--u));
    gap: calc(10 * var(--u));
}

.hxp-theatre .hxp-vc__btn {
    width: calc(40 * var(--u));
    height: calc(40 * var(--u));
}

.hxp-theatre .hxp-vc__btn svg {
    width: calc(18 * var(--u));
    height: calc(18 * var(--u));
}

.hxp-theatre .hxp-vp.is-paused .hxp-vp__screen::after {
    background-size: calc(120 * var(--u)) auto;
}

/* Once the player has grown, the grid behind it steps out of the way - and
   its scrollbar with it. */
.hxp-win[data-theatre] .hxp-win__scroll,
.hxp-win[data-theatre] .hxp-scrollbar {
    visibility: hidden;
}

/* The expanded window hides See more, which points at the page you are
   already on. See all content is a different button and has to show. */
.hxp-win__tools .hxp-btn--pill.hxp-theatre-back {
    display: inline-flex;
}

/* The phone stack gives the card no height to fill, so the player goes back
   into the flow: the picture at the poster's shape (player.js measures it into
   --ar) with the bar under it. */
@media (max-width: 1023.98px) {
    .hxp-media--playing .hxp-vp {
        position: relative;
        inset: auto;
    }

    .hxp-media--playing .hxp-vp__screen {
        flex: 0 0 auto;
        aspect-ratio: var(--ar, 16 / 9);
    }

    /* The card already is the widget's width here. */
    .hxp-vc__btn[data-vc="size"] {
        display: none;
    }
}

/* Full screen: the whole film, controls and bar kept to the foot. */
.hxp-vp:fullscreen {
    padding: 24px;
    gap: 16px;
    background: #000;
}

.hxp-vp:fullscreen video {
    object-fit: contain;
}

/* ==========================================================================
   Type: video-grid (Content)
   ========================================================================== */

.hxp-grid {
    margin: 0;
    padding: 0;
    list-style: none;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(calc(340 * var(--u)), 1fr));
    grid-auto-rows: 1fr;          /* rows share the frame instead of overrunning it */
    gap: calc(24 * var(--u)) calc(43 * var(--u));
    align-items: stretch;
    min-height: 0;
}

/* Wide: cards take their natural height and the media keeps the aspect the
   Figma draws it at. Sharing the frame between rows stretched the artwork to
   whatever height was left over. */
@container win (min-width: 740px) {
    .hxp-grid {
        grid-auto-rows: auto;
        align-items: start;
        row-gap: calc(34 * var(--u));
    }

    .hxp-card .hxp-media {
        flex: 0 0 auto;
        aspect-ratio: 405 / 248;
    }
}

/* "See more" points at the page you are already on. */
@container win (min-width: 740px) {
    .hxp-win__tools .hxp-btn--pill { display: none; }
}

.hxp-card .hxp-media {
    flex: 1 1 auto;
    min-height: 0;
}

.hxp-card {
    display: flex;
    flex-direction: column;
    gap: calc(6 * var(--u));
    padding-top: calc(14 * var(--u));
    border-top: 1px solid var(--glass-rule);

    /* Grid items default to min-height:auto, which floors them at their
       content height - the card could not shrink below its image's intrinsic
       size and overran the row. */
    min-height: 0;
    min-width: 0;
}

.hxp-card__title {
    margin: 0;
    font-size: calc(var(--t-lead) * var(--u));
    font-weight: 600;
    line-height: 1.2;
}

.hxp-card__description {
    margin: 0 0 calc(8 * var(--u));
    font-size: calc(var(--t-body) * var(--u));
    color: var(--ink-muted);
}

/* Narrow: the Content window shows one card filling the frame. */
@container win (max-width: 739.98px) {
    .hxp-grid {
        grid-template-columns: 1fr;
    }

    .hxp-card:nth-child(n + 2) {
        display: none;
    }

    .hxp-card {
        border-top: 0;
        padding-top: 0;
    }

    .hxp-card .hxp-eyebrow,
    .hxp-card .hxp-card__title,
    .hxp-card .hxp-card__description,
    .hxp-card .hxp-bar {
        display: none;
    }
}

/* ==========================================================================
   Type: services
   ========================================================================== */

/* The intro is the one place on the desktop that answers "what does this guy
   do", and at the shared 14-unit lead it was the quietest thing in the window.
   It is sized on its own here rather than by moving --t-lead, which the connect
   panel and every text window also read from. The lead lands near the numbered
   service titles, which is the size that already reads as a headline in this
   composition - the same 38 as .hxp-service__title, so the section headline is
   not left smaller than the rows beneath it when the window is expanded. */
.hxp-services__intro .hxp-lead {
    font-size: calc(38 * var(--u));
    line-height: 1.08;
    letter-spacing: -0.02em;
    text-wrap: balance;
    margin-bottom: calc(12 * var(--u));
}

.hxp-services__intro .hxp-prose {
    /* 16 rather than 17: at 17 the paragraph took two more lines and cost the
       window 35 units of height for no more legibility. */
    font-size: calc(16 * var(--u));
    line-height: 1.4;
}

.hxp-services__intro {
    flex: 0 0 auto;
    padding-bottom: calc(18 * var(--u));
    border-bottom: 1px solid var(--glass-rule);
}

/* Constrain the copy, not the block: max-width on the block dragged its rule
   in with it, so the separator stopped where the text stopped instead of
   running the full width of the window as the design has it. */
@container win (min-width: 1100px) {
    .hxp-services__intro > * {
        max-width: calc(600 * var(--u));
    }
}



/* Natural height, never a share of the frame. `flex: 1 1 0` let short windows
   crush these rows below their own content, so consecutive rows drew on top of
   each other - the window scrolls instead. */
.hxp-service {
    flex: 0 0 auto;
    display: grid;
    grid-template-columns: calc(150 * var(--u)) minmax(0, calc(390 * var(--u))) minmax(0, 1fr) minmax(0, 1fr);
    grid-template-areas: "mark title body bullets";
    gap: calc(28 * var(--u));
    align-items: start;
    padding: calc(24 * var(--u)) 0;
    border-bottom: 1px solid var(--glass-rule);
}

.hxp-service:last-child {
    border-bottom: 0;
}

.hxp-service__mark {
    grid-area: mark;
    display: flex;
    flex-direction: column;
    gap: calc(8 * var(--u));
}

.hxp-service__num {
    font-size: calc(30 * var(--u));
    font-weight: 600;
    line-height: 1;
    color: var(--ink-faint);
    font-variant-numeric: tabular-nums;
}

.hxp-service__icon {
    width: calc(35 * var(--u));
    height: calc(35 * var(--u));
    object-fit: contain;
}

.hxp-service__title {
    grid-area: title;
    margin: 0;
    font-size: calc(38 * var(--u));
    font-weight: 600;
    line-height: 1.06;
    letter-spacing: -0.02em;
    text-wrap: balance;
}

/* The service rows are the only part of the site that has to be read rather
   than looked at, so they are set two units above the shared body size - the
   same 16 as the intro paragraph, which makes the whole expanded window one
   reading size instead of three. Two units is the most this takes before the
   four-column row stops fitting its copy without wrapping oddly. */
.hxp-service__body {
    grid-area: body;
    margin: 0;
    font-size: calc(16 * var(--u));
    line-height: 1.45;
}

.hxp-service__bullets {
    grid-area: bullets;
    margin: 0;
    padding-left: calc(16 * var(--u));
    font-size: calc(16 * var(--u));
    line-height: 1.5;
}

.hxp-service__bullets li::marker {
    color: var(--ink-faint);
}

/* Between the two: wide enough for the expanded design, not wide enough for
   four columns of it. Four would leave the body copy setting three words to a
   line. The title takes the full width and the two text columns sit under it. */
@container win (min-width: 740px) and (max-width: 1099.98px) {
    .hxp-service {
        grid-template-columns: calc(120 * var(--u)) minmax(0, 1fr) minmax(0, 1fr);
        grid-template-areas:
            "mark  title   title"
            "mark  body    bullets";
        gap: calc(10 * var(--u)) calc(24 * var(--u));
    }

    .hxp-service__title {
        font-size: calc(30 * var(--u));
    }
}

/* Narrow: each card gets the frame to itself - one column, full width, and
   only as tall as its own content. The separators and the run of the rows are
   the same either way; only the column count and the type sizes change. */
@container win (max-width: 739.98px) {
    .hxp-card-slide {
        flex: 0 0 auto;
        padding-block: calc(4 * var(--u)) calc(16 * var(--u));
    }

    .hxp-services__intro {
        padding-bottom: calc(14 * var(--u));
    }

    .hxp-service {
        /* The areas have to go too: a four-cell template keeps four columns
           however grid-template-columns is set, so the rows stayed in four
           squeezed columns instead of stacking. */
        grid-template-areas: none;
        grid-template-columns: 1fr;
        gap: calc(8 * var(--u));
    }

    /* And so do the item placements. With no matching area, `grid-area: mark`
       is read as a line name instead, and grid invents implicit zero-width
       tracks for lines that do not exist - which is what stacked the parts of
       a row on top of each other. */
    .hxp-service > * {
        grid-area: auto;
    }

    /* The intro card starts right under the title bar and wants the tight
       4-unit top the slide padding gives it. A service row does not: it starts
       under the rule the row above it draws, and with the mark laid out as a
       row the icon is the tallest thing in it - taller than the number's text
       box, so it reaches higher - which left it all but stuck to that line. */
    .hxp-service.hxp-card-slide {
        padding-block: calc(18 * var(--u)) calc(16 * var(--u));
    }

    .hxp-service__mark {
        flex-direction: row;
        align-items: center;
        gap: calc(10 * var(--u));
    }

    .hxp-service__num {
        font-size: calc(20 * var(--u));
    }

    .hxp-service__title {
        font-size: calc(22 * var(--u));
    }
}

/* ==========================================================================
   Type: media-player
   ========================================================================== */

.hxp-player {
    display: flex;
    flex-direction: column;
    gap: calc(12 * var(--u));
    height: 100%;
}

/* The transport sits in the title bar, where the design puts it. */
/* On the right, where every other widget keeps its tools, so the name leads on
   the left as it does everywhere else. */
.hxp-player__transport {
    display: flex;
    align-items: center;
    gap: calc(10 * var(--u));
    margin-left: auto;
    flex-shrink: 0;
}

.hxp-win--media-player .hxp-win__tools {
    display: none;
}

.hxp-player__btn {
    display: grid;
    place-items: center;
    width: calc(20 * var(--u));
    height: calc(20 * var(--u));
    color: var(--ink);
}

.hxp-player__btn svg {
    width: calc(15 * var(--u));
    height: calc(15 * var(--u));
}

.hxp-player__stage {
    flex: 1 1 auto;
    min-height: 0;
    border-radius: calc(4 * var(--u));
    overflow: hidden;
    background: rgba(255, 255, 255, 0.06);
}

.hxp-player__stage img,
.hxp-player__stage video {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.hxp-player__bar {
    flex-shrink: 0;
}

/* The film on the stage. Nothing sits over it - the transport in the title bar
   is the way in - so the still reads as a paused frame, not a thumbnail. */
.hxp-player__video {
    display: block;
    cursor: pointer;
}

.hxp-player__track {
    flex-shrink: 0;
}

/* A YouTube, Vimeo or Bunny film: its still, then the provider's player. */
.hxp-player__embed {
    position: relative;
    width: 100%;
    height: 100%;
    background: #000;
    cursor: pointer;
}

.hxp-player__embed img,
.hxp-player__embed iframe {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
    object-fit: cover;
}

/* The lit transport button: whichever of play and pause is in force. */
.hxp-player__btn.is-on {
    color: var(--harry-yellow);
}

/* The player's bar is a gradient that runs out rather than a hard-edged
   progress fill - it reads as playback, not as a percentage. */
.hxp-player__bar .hxp-bar__fill {
    width: 100%;
    background: linear-gradient(
        90deg,
        var(--harry-yellow) 0%,
        rgba(237, 255, 0, 0.88) 16%,
        rgba(213, 226, 0, 0.55) 42%,
        rgba(160, 170, 0, 0.22) 68%,
        rgba(237, 255, 0, 0) 88%
    );
}

/* Once the film has started the bar is its progress, drawn like every other
   progress bar on the site. */
.hxp-player.is-started .hxp-player__bar .hxp-bar__fill {
    width: var(--fill);
    background: var(--harry-yellow);
}

/* ==========================================================================
   Type: gimmick — the big red button
   ========================================================================== */

.hxp-gimmick {
    display: grid;
    place-items: center;
    height: 100%;
    min-height: 0;
    overflow: hidden;
}

.hxp-bigred {
    display: block;
    aspect-ratio: 1;
    block-size: min(calc(83 * var(--u)), 100%);
    inline-size: auto;
    max-inline-size: 100%;
    transition: transform 0.09s var(--ease);
}

.hxp-bigred img {
    width: 100%;
    height: 100%;
    object-fit: contain;
}

.hxp-bigred:active {
    transform: translateY(calc(2 * var(--u))) scale(0.97);
}

/* Once it has been pressed there is nothing left to press. */
.hxp-bigred:disabled {
    cursor: default;
}

/* ==========================================================================
   Type: contact — Clippy
   ========================================================================== */

.hxp-clippy {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(12 * var(--u));
    height: 100%;
    text-align: center;
}

.hxp-clippy__img {
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
    display: grid;
    place-items: center;
}

/* Bound on both axes and let the intrinsic ratio pick the size, so the
   artwork scales down to fit rather than being cropped by its box. */
.hxp-clippy__img img {
    width: auto;
    height: auto;
    max-width: min(calc(119 * var(--u)), 100%);
    max-height: 100%;
    object-fit: contain;
}

.hxp-clippy .hxp-btn {
    width: 100%;
}

/* The contact details, for the phone stack only - see contact.php and
   responsive.css. On the desktop the button opens them as a panel. */
.hxp-clippy__connect {
    display: none;
    width: 100%;
}

/* --------------------------------------------------------------------------
   Narrow windows — the gimmick frame is 117 units wide and Clippy 156. At the
   standard 18 units of padding and a 16-unit title there is nothing left to
   set the title in, so both tighten and the title wraps rather than truncates.
   -------------------------------------------------------------------------- */

.hxp-win--narrow {
    --win-pad: 13;
}

.hxp-win--narrow .hxp-win__bar {
    height: auto;
    min-height: calc(34 * var(--u));
    align-items: flex-start;
    padding-block: calc(10 * var(--u)) calc(8 * var(--u));
}

.hxp-win--gimmick {
    --win-pad: 9;
}

.hxp-win--gimmick .hxp-win__title {
    font-size: calc(13 * var(--u));
}

/* Clippy and the red button are fixed-size widgets, and their titles read as
   labels for the thing underneath - so they centre.

   The title has to span the bar for that: .hxp-win__tools carries
   margin-left:auto, which eats the free space and pins the title left however
   the bar is justified. Neither window renders any tools, so it goes. */
.hxp-win--contact .hxp-win__tools,
.hxp-win--gimmick .hxp-win__tools {
    display: none;
}

.hxp-win--contact .hxp-win__title,
.hxp-win--gimmick .hxp-win__title {
    flex: 1 1 auto;
    text-align: center;
}

.hxp-win--narrow .hxp-win__title {
    font-size: calc(14 * var(--u));
    line-height: 1.18;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    text-wrap: balance;
}

/* ==========================================================================
   Type: connect — the contact details panel
   ========================================================================== */

.hxp-connect {
    display: flex;
    flex-direction: column;
    gap: calc(8 * var(--u));
}

.hxp-connect__lead {
    margin: 0 0 calc(4 * var(--u));
    font-size: calc(var(--t-body) * var(--u));
    line-height: 1.35;
    color: var(--ink-muted);
}

.hxp-connect__link {
    width: 100%;
    height: calc(26 * var(--u));
    font-size: calc(13 * var(--u));
}

.hxp-connect__socials {
    display: flex;
    gap: calc(8 * var(--u));
}

.hxp-connect__social {
    flex: 1 1 0;
    height: calc(26 * var(--u));
}

.hxp-connect__social svg {
    width: calc(15 * var(--u));
    height: calc(15 * var(--u));
}

.hxp-scrim {
    position: absolute;
    inset: 0 0 var(--taskbar-h) 0;
    z-index: 800;
    background: rgba(6, 10, 18, 0.5);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.25s var(--ease), visibility 0s linear 0.25s;
}

.hxp-desktop.has-modal .hxp-scrim {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.25s var(--ease), visibility 0s;
}
