/*
Theme Name: MAISON ED
Theme URI: https://maisoned.com
Author: Daffa Pramuditya
Description: Custom theme for MAISON ED, luxury hospitality marketing and PR consultancy.
Version: 1.26.2
Requires at least: 6.5
Requires PHP: 8.1
Text Domain: maisoned
*/

/* =========================================================================
   TOKENS

   Palette: four brand hexes (#76220B, #4D381F, #D7ECE2, #D8D6CB). Every
   other colour is a tint or shade of those four; no new hues.

   Type: Inter (body), Archivo at 125% width (wordmark), Questrial
   (headings), Lora (services grid titles and numerals).

   Type sizes: px / 1920 * 100 = vw with clamp() floors.
   ========================================================================= */

:root {
    /* Client palette, exact */
    --paper:      #D8D6CB;  /* Light Beige â€” base ground */
    --rust:       #76220B;  /* Dark Brown â€” headings */
    --ink:        #4D381F;  /* Dark Grey Brown â€” body text, footer ground */
    --sage:       #D7ECE2;  /* Sage Green â€” buttons */

    /* Derived tints/shades of the four, no new hues */
    --paper-pale: #F1EFE7;  /* cream: cards, founder ground */
    --paper-deep: #CFCCBE;  /* darker beige: statement panel */
    --ink-soft:   #7C7261;  /* quiet body tone */
    --ink-faint:  #98917E;  /* numerals, rules on cream */
    --rule:       #C2BFAF;  /* hairline on beige */
    --rule-pale:  #DEDBCD;  /* hairline on cream */
    --sage-deep:  #C4DFD2;  /* button hover */

    --font-body:    'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    --font-display: 'Archivo', 'Arial Black', sans-serif;
    --font-heading: 'Questrial', 'Century Gothic', var(--font-body);
    --font-serif:   'Lora', Georgia, serif;

    --t-caption: clamp(11px, 0.7292vw, 14px);
    --t-body:    clamp(15px, 0.9375vw, 18px);
    --t-lead:    clamp(17px, 1.1458vw, 22px);
    --t-title:   clamp(19px, 1.3542vw, 26px);
    --t-heading: clamp(26px, 2.1875vw, 42px);
    --t-name:    clamp(24px, 2.0833vw, 40px);

    /* House curve (easeOutQuad family) â€” tuned empirically, see CLAUDE.md
       before changing. */
    --ease:     cubic-bezier(.11, .7, .6, .94);
    --ease-out: cubic-bezier(.39, .575, .565, 1);

    --gutter:  clamp(1.25rem, 3.125vw, 60px);
    --measure: 62ch;
}

/* =========================================================================
   BASE
   ========================================================================= */

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

body {
    /* The window's width without the scrollbar, for anything that has to
       bleed edge to edge. */
    --window: 100%;
    margin: 0;
    background: var(--ground, var(--paper));
    transition: background-color .9s var(--ease);
    color: var(--ink);
    font-family: var(--font-body);
    font-size: var(--t-body);
    font-weight: 300;
    line-height: 1.7;
    -webkit-font-smoothing: antialiased;
    /* `hidden` here makes the body a scroll container, and a scroll container
       that never scrolls: every position:sticky inside the page then anchors
       to it and simply never sticks. `clip` cuts the same horizontal overflow
       without becoming one. The `hidden` line stays first as the fallback for
       browsers that do not know `clip`. */
    overflow-x: hidden;
    overflow-x: clip;
}

img, video, svg { max-width: 100%; height: auto; display: block; }
a { color: inherit; }

::selection { background: var(--sage); color: var(--ink); }

h1, h2, h3 { margin: 0; font-weight: 300; line-height: 1.15; }
p { margin: 0 0 1em; max-width: var(--measure); }

.wrap { width: min(100% - var(--gutter) * 2, 88rem); margin-inline: auto; }

/* Short pages (a young journal) must still put the footer on the floor,
   or the canvas paints bare ground beneath it. */
.issue {
    min-height: 100svh;
    display: flex;
    flex-direction: column;
}
.issue > main { flex: 1; }

.skip { position: absolute; left: -9999px; }
.skip:focus {
    left: var(--gutter);
    top: 1rem;
    z-index: 99;
    background: var(--ink);
    color: var(--paper);
    padding: .5rem 1rem;
}

:where(a, button, input, textarea, summary, [tabindex]):focus-visible {
    outline: 2px solid var(--rust);
    outline-offset: 3px;
}

/* â”€â”€ Voices â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

   Three registers: the heading voice (rust, tracked light caps), the
   label voice (small tracked caps), the body voice (grey-brown,
   justified in long passages).                                        */

.sect-head {
    font-family: var(--font-heading);
    font-weight: 400;
    font-size: var(--t-heading);
    letter-spacing: .22em;
    text-transform: uppercase;
    color: var(--rust);
    max-width: none;
    margin: 0;
}
.sect-sub {
    font-size: var(--t-lead);
    color: var(--ink);
    max-width: none;
    margin: .35em 0 0;
}
/* On the service page this line names the rail below it and is a heading, so
   it would otherwise pick up the 1.15 the house sets for headings and close up
   by 9px. The journal's standfirst keeps the paragraph it already was. */
h2.sect-sub { line-height: 1.7; }

.label {
    font-size: var(--t-caption);
    font-weight: 400;
    letter-spacing: .2em;
    text-transform: uppercase;
}

/* A section that is named for a reader who cannot see it. Two sections here
   carry no visible title by design, and without a name they leave a hole in
   the page's outline that a screen reader and a crawler both fall through. */
.u-quiet {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* Sage pill button, drawn straight from DISCOVER MORE / CONNECT WITH ME. */
.btn {
    display: inline-block;
    font-family: var(--font-body);
    font-weight: 400;
    font-size: clamp(11px, 0.7292vw, 14px);
    letter-spacing: .14em;
    text-transform: uppercase;
    text-decoration: none;
    color: var(--ink);
    background: var(--sage);
    border: 0;
    border-radius: 999px;
    padding: 1.1em 2.6em;
    cursor: pointer;
    transition: background-color .45s var(--ease), transform .45s var(--ease);
}
.btn:hover { background: var(--sage-deep); transform: translateY(-2px); }

/* Quiet text link (journal back-links, mail addresses). */
.link {
    display: inline-block;
    font-size: var(--t-caption);
    letter-spacing: .14em;
    text-transform: uppercase;
    text-decoration: none;
    padding-bottom: 4px;
    background: linear-gradient(currentColor, currentColor) left bottom / 100% 1px no-repeat;
    transition: background-size .5s var(--ease), color .5s var(--ease);
}
.link:hover { background-size: 0 1px; background-position: right bottom; }

/* =========================================================================
   REVEAL â€” unchanged grammar: rise from mask, house curve, .2s stagger
   ========================================================================= */

.reveal-on-scroll {
    opacity: 0;
    transform: translate3d(0, 1.5rem, 0);
    transition: opacity .75s var(--ease), transform .75s var(--ease);
}
.reveal-on-scroll.is-in-view { opacity: 1; transform: none; }

.reveal-on-scroll[data-reveal="rise"] { transform: translate3d(0, 2rem, 0); }

/* The wipe clips an inner child, never the observed element itself.
   A fully clipped element has zero visible area, so IntersectionObserver
   reports isIntersecting:false forever and the reveal can never fire. */
.reveal-on-scroll[data-reveal="mask"] { opacity: 1; transform: none; }
.reveal-on-scroll[data-reveal="mask"] > * {
    clip-path: inset(0 100% 0 0);
    transition: clip-path 1.3s var(--ease);
}
.reveal-on-scroll[data-reveal="mask"].is-in-view > * { clip-path: inset(0 0 0 0); }

/* The wipe can be asked to open from another edge, so a run of figures down
   one page never repeats the same left to right sweep. */
.reveal-on-scroll[data-reveal="mask"][data-reveal-from="right"] > * { clip-path: inset(0 0 0 100%); }
.reveal-on-scroll[data-reveal="mask"][data-reveal-from="bottom"] > * { clip-path: inset(100% 0 0 0); }
.reveal-on-scroll[data-reveal="mask"][data-reveal-from="right"].is-in-view > *,
.reveal-on-scroll[data-reveal="mask"][data-reveal-from="bottom"].is-in-view > * { clip-path: inset(0 0 0 0); }

/* A photograph can also simply settle: it arrives a little large and comes
   to rest, which reads differently again from a wipe. */
.reveal-on-scroll[data-reveal="zoom"] { opacity: 1; transform: none; overflow: hidden; }
.reveal-on-scroll[data-reveal="zoom"] > * {
    opacity: 0;
    transform: scale(1.06);
    transition: opacity 1.1s var(--ease), transform 1.4s var(--ease);
}
.reveal-on-scroll[data-reveal="zoom"].is-in-view > * { opacity: 1; transform: none; }

.reveal-on-scroll[data-reveal="lines"] { opacity: 1; transform: none; }
.reveal-on-scroll[data-reveal="lines"] .line { display: block; overflow: hidden; }
.reveal-on-scroll[data-reveal="lines"] .line__inner {
    display: block;
    transform: translate3d(0, 100%, 0);
    transition: transform .75s var(--ease);
}
.reveal-on-scroll[data-reveal="lines"].is-in-view .line__inner { transform: none; }

.no-scroll-reveal .reveal-on-scroll > *,
.no-scroll-reveal .reveal-on-scroll {
    opacity: 1;
    transform: none;
    clip-path: none;
}

/* =========================================================================
   SCRUB â€” tall section, sticky stage; CSS pins, scrub.js writes transforms
   ========================================================================= */

[data-scrub] {
    position: relative;
    height: calc(var(--scrub-vh, 300) * 1svh);
}
[data-scrub] .scrub__stage {
    position: sticky;
    top: 0;
    height: 100svh;
    overflow: hidden;
}

/* Without JS (or under reduced motion) a pinned section is just a static
   frame, so the scroll runway collapses to one screen. */
html:not(.js) [data-scrub] { height: auto; }
@media (prefers-reduced-motion: reduce) {
    [data-scrub] { height: auto !important; }   /* beats variant height rules */
}

/* =========================================================================
   MASTHEAD

   Fixed; hides on scroll down, returns on scroll up (masthead.js). Over
   photographic sections (.is-dark) it inverts to white.
   ========================================================================= */

.masthead {
    position: fixed;
    top: 0; left: 0; right: 0;
    z-index: 10;
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    padding: clamp(1.2rem, 2vw, 2.2rem) var(--gutter);
    /* Follows the ambient ground (ambient.js writes --ground on body), so the
       returning masthead never paints a beige band over a pale section. */
    background: linear-gradient(to bottom, var(--ground, var(--paper)) 15%, transparent);
    transition: transform .55s var(--ease);
}
.masthead--hidden { transform: translateY(-110%); }
.masthead--hidden:focus-within { transform: none; }

.masthead__mark {
    font-family: var(--font-display);
    font-weight: 800;
    font-stretch: 125%;
    font-size: 14px;
    letter-spacing: .1em;
    text-decoration: none;
    color: var(--ink);
}

.masthead__nav { display: flex; gap: clamp(1.2rem, 2.5vw, 2.8rem); }
.masthead__nav a {
    font-size: 12px;
    font-weight: 400;
    letter-spacing: .2em;
    text-transform: uppercase;
    text-decoration: none;
    color: var(--ink);
    background: linear-gradient(currentColor, currentColor) left bottom / 0 1px no-repeat;
    padding-bottom: 3px;
    transition: background-size .45s var(--ease), color .4s var(--ease);
}
.masthead__nav a:hover { background-size: 100% 1px; }
.masthead__nav a.is-current { background-size: 100% 1px; }

/* White over photographs. */
.masthead--inverse { background: none; }
.masthead--inverse .masthead__mark,
.masthead--inverse .masthead__nav a,
.masthead--inverse .masthead__burger {
    color: #fff;
    text-shadow: 0 1px 24px rgba(0, 0, 0, .25);
}

/* Two lines that cross when the menu is open. JS swaps the plain nav for
   this under 900px; without JS the row stays. */
.masthead__burger {
    display: none;
    position: relative;
    width: 44px;
    height: 44px;
    margin: -10px -8px;
    border: 0;
    background: none;
    color: var(--ink);
    cursor: pointer;
}
.masthead__burger span {
    position: absolute;
    left: 9px;
    top: 50%;
    width: 26px;
    height: 2px;
    background: currentColor;
    transition: transform .45s var(--ease);
}
.masthead__burger span:first-child { transform: translateY(-4px); }
.masthead__burger span:last-child { transform: translateY(4px); }
html.menu-open .masthead__burger span:first-child { transform: rotate(45deg); }
html.menu-open .masthead__burger span:last-child { transform: rotate(-45deg); }

/* The masthead normally sits under overlays; while the menu is open it
   must ride above the ink screen so the burger stays reachable. */
html.menu-open .masthead { z-index: 65; background: none; }
html.menu-open .masthead__mark,
html.menu-open .masthead__burger { color: var(--paper-pale); }
html.menu-open .masthead--hidden { transform: none; }

.menu {
    position: fixed;
    inset: 0;
    z-index: 60;
    background: var(--ink);
    display: grid;
    /* Centred while there is room, from the top once there is not: on a phone
       held sideways the four names and the address stand 490px tall in a 390px
       window, and plain centring put 50px past each edge with no way to reach
       them. `safe` drops the centring exactly at that point, and the overlay
       scrolls for the remainder. */
    align-content: safe center;
    justify-items: start;
    padding: clamp(1rem, 4vh, 2.5rem) var(--gutter);
    overflow-y: auto;
    overscroll-behavior: contain;
    opacity: 0;
    transition: opacity .5s var(--ease);
}
/* display:grid above would beat the [hidden] UA style. */
.menu[hidden] { display: none; }
html.menu-open .menu { opacity: 1; }
.menu__nav { display: grid; gap: clamp(.5rem, 1.8vh, 1.2rem); }
.menu__nav a {
    font-family: var(--font-heading);
    font-size: clamp(32px, 8.5vw, 60px);
    letter-spacing: .1em;
    text-transform: uppercase;
    text-decoration: none;
    color: var(--paper-pale);
    opacity: 0;
    transform: translate3d(0, 1.1em, 0);
    transition: transform .65s var(--ease), opacity .65s var(--ease);
}
.menu__nav a.is-current { color: var(--paper); }
html.menu-open .menu__nav a { opacity: 1; transform: none; }
html.menu-open .menu__nav a:nth-child(2) { transition-delay: .06s; }
html.menu-open .menu__nav a:nth-child(3) { transition-delay: .12s; }
html.menu-open .menu__nav a:nth-child(4) { transition-delay: .18s; }
.menu__mail {
    margin: clamp(2rem, 6vh, 3.5rem) 0 0;
    opacity: 0;
    transition: opacity .65s var(--ease) .26s;
}
html.menu-open .menu__mail { opacity: 1; }
.menu__mail a {
    color: #B9AE9D;
    text-decoration: none;
    font-size: var(--t-caption);
    letter-spacing: .14em;
    text-transform: uppercase;
}

/* =========================================================================
   COVER â€” full-bleed photograph, extended wordmark centred; a pinned
   scrub parts the words while the photograph exits through a closing
   iris and rests as a circular medallion in the gap
   ========================================================================= */

/* The composition's optical centre sits slightly above the middle, so the
   stage carries bottom padding under centred items. */
.cover__stage { display: grid; place-items: center; padding-bottom: 6.5svh; }

/* Two stacked wordmark layers share the stage cell and crossfade as the
   photograph exits: white over the image, ink once the ground is through. */
.cover__words { grid-area: 1 / 1; position: relative; z-index: 3; }
.cover__words--ink { opacity: 0; }
.cover__words--ink .cover__wordmark { color: var(--ink); }
.cover__word { display: inline-block; }

/* The tagline sits under the lockup and arrives only as the cover finishes.
   Centring is done with left/right rather than a transform, because the scrub
   writes the transform property and would overwrite it. */
.cover__tagline {
    position: absolute;
    /* The condensed plate is 12.4% of the stage, so on a narrow screen it is
       taller than the type it sits between and hangs below the lockup, and
       this line would run behind it. Clear whichever of the two reaches
       lower: the term is zero once the wordmark is the taller of them. */
    top: calc(100% + clamp(.9rem, 2.2vh, 1.8rem) + max(0px, 6.2svh - min(4.8vw, 92px)));
    left: 0;
    right: 0;
    margin: 0;
    /* The house measure is set in `ch`, and this line's letter spacing is wider
       than its type is tall, so that measure resolved to the width of the words
       themselves and pinned the box to the left edge of the lockup. */
    max-width: none;
    text-align: center;
    font-size: clamp(10px, .78vw, 13px);
    letter-spacing: .3em;
    text-transform: uppercase;
    color: #fff;
    opacity: 0;
}
.cover__words--ink .cover__tagline { color: var(--ink); }
/* No scroll to drive it: the still frame carries the line instead. */
html:not(.js) .cover__tagline--still { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
    .cover__tagline--still { opacity: 1; }
}

.cover__plate {
    position: absolute;
    inset: 0;
    margin: 0;
    overflow: hidden;
    transform-origin: center;
    /* The plate starts as the viewport, so condensing it uniformly leaves a
       window shaped like the screen: wide on a desktop, a tall slot on a
       phone. Trim it as it closes so the window lands on the film's own 16/9
       at every size, height on a screen taller than that, width on one wider.
       Both terms are zero at the start, so the opening is still full bleed,
       and only one of them is ever doing anything. */
    clip-path: inset(
        calc(var(--clip, 0) * max(0px, (100svh - 56.25vw) / 2))
        calc(var(--clip, 0) * max(0px, (100vw - 177.78svh) / 2))
    );
}
.cover__plate img,
.cover__plate video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    animation: plate-in 1.8s var(--ease) both;
}
@keyframes plate-in {
    from { opacity: 0; transform: scale(1.05); }
    to   { opacity: 1; transform: none; }
}

/* The window's changing hands: the imagery hard-cuts through the rooms
   (scrub.js frame track shows one child per slice). The first child is an
   empty pane, so the film underneath opens the sequence; the last
   photograph stays and lands the chip. Living inside the plate, the cuts
   inherit its condense for free. */
.cover__frames {
    position: absolute;
    inset: 0;
    pointer-events: none;
}
.cover__frame {
    position: absolute;
    inset: 0;
    display: block;
    visibility: hidden;
}
.cover__frame img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.cover__wordmark {
    position: relative;
    z-index: 2;
    font-family: var(--font-display);
    font-weight: 800;
    font-stretch: 125%;
    font-size: min(9.6vw, 184px);                           /* ~74% viewport width */
    line-height: 1;
    letter-spacing: .02em;
    white-space: nowrap;
    color: #fff;
    margin: 0;
}

/* =========================================================================
   STATEMENT â€” photograph left, beige panel right, rust caps heading
   centred, justified body, sage pill
   ========================================================================= */

.statement {
    display: grid;
    grid-template-columns: 56fr 44fr;
    min-height: 100svh;
}
.statement__plate { position: relative; margin: 0; overflow: hidden; }
.statement__plate img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.statement__panel {
    background: var(--paper-deep);
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: clamp(3.5rem, 8vh, 7rem) clamp(1.5rem, 4.5vw, 5.5rem);
}
.statement__head {
    font-family: var(--font-heading);
    font-weight: 400;
    font-size: clamp(16px, 1.25vw, 24px);
    line-height: 1.55;
    letter-spacing: .08em;
    text-transform: uppercase;
    text-align: center;
    color: var(--rust);
    max-width: none;
    margin: 0 0 2.2em;
}
/* Explanatory copy runs ragged right, and no word is ever broken across a
   line. Justification was measured against every column this site actually
   has and none of them can carry it: at a phone width the widest gap reaches
   5 to 13 times a normal word space, and even the widest column here, the
   journal document at 736px, still opens gaps of 2.6. Hyphenation hides that
   by splitting words instead, which is worse. text-wrap keeps the ragged edge
   even and stops a last line falling to a single word. */
.statement__body p,
.founder__body p,
.about-drift__text p,
.jindex__standfirst,
.svc-panel__desc,
.gallery__desc,
.entry__body p,
.contact__note {
    text-align: left;
    hyphens: manual;
    text-wrap: pretty;
}

.statement__body p {
    color: var(--ink);
    max-width: none;
    margin: 0 0 1.2em;
}
.statement__cta {
    align-self: center;
    margin-top: 1.8rem;
}

/* =========================================================================
   SECTION HEAD BLOCK â€” SERVICES / ABOUT / CONTACT openers
   ========================================================================= */

.page-head {
    padding: clamp(6rem, 12vh, 8.5rem) var(--gutter) clamp(1.8rem, 3.5vw, 3.5rem);
}
.page-head--flush { padding-bottom: 0; }
.page-head__date { color: var(--ink-soft); margin: 0 0 .6rem; }

/* Photographic page opener, one composed frame: the title sits in the
   image, the way the client's tone reference heads its pages. */
.page-cover {
    position: relative;
    height: min(86svh, 880px);
    overflow: hidden;
}
.page-cover__plate {
    position: absolute;
    inset: 0;
    margin: 0;
}
.page-cover__plate img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    animation: plate-in 1.8s var(--ease) both;
}
.page-cover::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(to top, rgba(45, 32, 17, .38), transparent 45%);
}
.page-cover__body {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: grid;
    place-content: center;
    text-align: center;
    padding: 0 var(--gutter);
    color: var(--paper-pale);
}
.page-cover__head {
    font-family: var(--font-heading);
    font-weight: 400;
    font-size: clamp(30px, 3.2vw, 58px);
    letter-spacing: .3em;
    text-indent: .3em;               /* re-centres tracked caps */
    text-transform: uppercase;
    color: #fff;
    text-shadow: 0 1px 34px rgba(45, 32, 17, .4);
    margin: 0 0 .5em;
}
.page-cover__sub {
    font-size: var(--t-lead);
    color: rgba(241, 239, 231, .94);
    text-shadow: 0 1px 24px rgba(45, 32, 17, .4);
    max-width: none;
    margin: 0;
}

/* =========================================================================
   GALLERY â€” portrait cards on one rail bleeding off the right edge, white
   labels bottom left, dark circular arrows; the track is a native scroller
   ========================================================================= */

/* The grid that used to follow the rail is gone, so the rail carries the
   room under it that the grid used to give. */
.gallery {
    position: relative;
    padding-bottom: clamp(3rem, 6vw, 6rem);
}

.gallery__track {
    display: flex;
    gap: clamp(1rem, 1.6vw, 1.8rem);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    padding: 0 var(--gutter);
    scrollbar-width: none;
}
.gallery__track::-webkit-scrollbar { display: none; }

.gallery__card {
    position: relative;
    display: block;
    flex: none;
    width: clamp(240px, 24vw, 380px);
    aspect-ratio: 10 / 14;
    padding: 0;
    margin: 0;
    border: 0;
    background: none;
    overflow: hidden;
    scroll-snap-align: start;
    /* The track carries its gutter as padding, so the snap point has to be
       pushed back out by the same amount or the first card would snap flush
       to the viewport edge. */
    scroll-margin-inline-start: var(--gutter);
    text-align: left;
    cursor: pointer;
}
.gallery__card img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    pointer-events: none;
    -webkit-user-drag: none;
    transition: transform 1.2s var(--ease);
}
.gallery__card:hover img { transform: scale(1.04); }
.gallery__caption {
    position: absolute;
    left: clamp(1rem, 1.6vw, 1.6rem);
    right: clamp(1rem, 1.6vw, 1.6rem);
    bottom: clamp(.9rem, 1.5vw, 1.5rem);
    z-index: 2;
}
.gallery__label {
    display: block;
    color: #fff;
    font-size: var(--t-lead);
    font-weight: 300;
    line-height: 1.3;
    text-shadow: 0 1px 28px rgba(0, 0, 0, .45);
}
/* Only the label is on the card while the script is running; the panel
   carries the description. Without JS there is no panel, so the words come
   back here instead of being lost with it. */
.gallery__desc { display: none; }
.gallery__card::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(to top, rgba(30, 20, 10, .38), transparent 45%);
}

.gallery__btn {
    position: absolute;
    top: 50%;
    z-index: 3;
    transform: translateY(-50%);
    width: clamp(44px, 3.4vw, 58px);
    aspect-ratio: 1;
    border: 0;
    border-radius: 50%;
    background: rgba(77, 56, 31, .88);
    color: #fff;
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
    display: grid;
    place-items: center;
    transition: background-color .4s var(--ease), opacity .4s var(--ease);
}
.gallery__btn:hover { background: var(--ink); }
.gallery__btn[disabled] { opacity: .35; cursor: default; }
.gallery__btn--prev { left: calc(var(--gutter) * .4); }
.gallery__btn--next { right: calc(var(--gutter) * .4); }
.gallery__btn svg { width: 18px; height: 18px; }

/* =========================================================================
   SERVICES INDEX â€” the landing's eight, set as a printed list against one
   plate. The list holds still; the plate carries the photograph of the
   line being read, and that crossfade is the section's only movement
   ========================================================================= */

/* The section below carries the air between them; adding it at both ends
   made this one boundary read twice as open as every other. */
.svcindex {
    display: grid;
    grid-template-columns: minmax(0, 27rem) minmax(0, 1fr);
    align-items: start;
    gap: clamp(2rem, 5vw, 6rem);
    padding: clamp(4rem, 8vw, 8rem) var(--gutter) 0;
}
/* No fill behind the stills: the plate is revealed by wiping its children,
   so anything painted here would be a coloured panel sitting in the open
   part of the wipe until the photograph finished arriving. */
.svcindex__plate {
    position: relative;
    margin: 0;
    aspect-ratio: 4 / 5;
    overflow: hidden;
}
.svcindex__still {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
    transition: opacity .9s var(--ease);
}
.svcindex__still.is-on { opacity: 1; }

.svcindex__rows {
    list-style: none;
    margin: 0;
    padding: 0;
    border-top: 1px solid var(--rule);
}
.svcindex__row { border-bottom: 1px solid var(--rule); }
.svcindex__row a {
    display: grid;
    grid-template-columns: 2.6rem minmax(0, 1fr) auto;
    align-items: center;
    gap: clamp(.6rem, 1.4vw, 1.4rem);
    padding: clamp(1rem, 1.7vw, 1.7rem) 0;
    text-decoration: none;
    color: var(--ink-soft);
    transition: color .45s var(--ease);
}
.svcindex__row a:hover,
.svcindex__row a:focus-visible { color: var(--ink); }
.svcindex__no {
    font-family: var(--font-serif);
    font-style: italic;
    font-size: var(--t-caption);
    letter-spacing: .16em;
    color: var(--ink-faint);
}
.svcindex__title {
    font-family: var(--font-serif);
    font-weight: 500;
    font-size: clamp(18px, 1.65vw, 31px);
    line-height: 1.25;
    color: inherit;
}
.svcindex__arrow {
    opacity: 0;
    transform: translate3d(-.5rem, 0, 0);
    transition: opacity .45s var(--ease), transform .45s var(--ease);
}
.svcindex__arrow svg { display: block; width: 21px; height: 21px; }
.svcindex__row a:hover .svcindex__arrow,
.svcindex__row a:focus-visible .svcindex__arrow { opacity: .45; transform: none; }

.svc-panel {
    position: fixed;
    inset: 0;
    z-index: 70;
    overflow: hidden;
    transition: clip-path .72s var(--ease);
}
/* The media box travels from the card's rectangle to the viewport, so the
   photograph that grows is the very one the visitor clicked; svc-launch.js
   writes the box, this only paces it in step with the clip. */
.svc-panel__media {
    position: absolute;
    margin: 0;
    overflow: hidden;
    /* Transform and radius travel with the box, so a card chosen at an
       angle or with rounded corners straightens as the room opens. */
    transition: top .72s var(--ease), left .72s var(--ease), width .72s var(--ease),
                height .72s var(--ease), transform .72s var(--ease), border-radius .72s var(--ease);
}
.svc-panel__media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Ink shade rising from the floor of the room, only as far as the text
   needs it. */
.svc-panel__scrim {
    position: absolute;
    inset: 0;
    background: linear-gradient(to top, rgba(45, 32, 17, .82), rgba(45, 32, 17, .25) 45%, transparent 70%);
    opacity: 0;
    transition: opacity .6s var(--ease);
}
.svc-panel--open .svc-panel__scrim { opacity: 1; }

.svc-panel__body {
    position: absolute;
    left: var(--gutter);
    right: var(--gutter);
    bottom: clamp(2.5rem, 6vh, 4.5rem);
    max-width: 46rem;
    color: var(--paper-pale);
}
/* The stack is hung from the foot of a panel that is fixed and never scrolls,
   so on a short screen it grows upward past the top edge and what leaves the
   frame cannot be brought back: measured with the real copy, the block began
   85px above the viewport at 844x390 and the title itself was cut at 740x360,
   while at 320x568 it ran under the close button. Held off the top as well as
   the foot, it keeps sitting at the foot while there is room and scrolls only
   once there is not. */
@media (max-height: 640px), (max-width: 360px) {
    .svc-panel__body {
        top: 5rem;
        display: flex;
        flex-direction: column;
        justify-content: flex-end;
        overflow-y: auto;
        overscroll-behavior: contain;
    }
}
/* The service's own mark, carried over from the card. */
.svc-panel__icon {
    display: block;
    width: 30px;
    height: 30px;
    color: #fff;
    opacity: .85;
    margin-bottom: clamp(1rem, 1.8vw, 1.6rem);
}
.svc-panel__icon svg { width: 100%; height: 100%; }
.svc-panel__no {
    display: block;
    font-family: var(--font-serif);
    font-style: italic;
    font-size: var(--t-caption);
    letter-spacing: .2em;
    color: var(--paper);
    margin-bottom: 1rem;
}
.svc-panel__title {
    font-family: var(--font-serif);
    font-weight: 500;
    font-size: clamp(28px, 2.9vw, 54px);
    line-height: 1.15;
    color: #fff;
    max-width: 16ch;
    margin: 0 0 1rem;
}
.svc-panel__desc {
    font-size: var(--t-lead);
    line-height: 1.7;
    max-width: 46ch;
    color: rgba(241, 239, 231, .92);
    margin: 0 0 1.8rem;
}
.svc-panel__actions {
    display: flex;
    align-items: center;
    gap: clamp(1.2rem, 2.5vw, 2.2rem);
}
.svc-panel__all { color: var(--paper-pale); }

.svc-panel__icon,
.svc-panel__no,
.svc-panel__title,
.svc-panel__desc,
.svc-panel__actions {
    opacity: 0;
    transform: translate3d(0, 1.4rem, 0);
    transition: opacity .7s var(--ease), transform .7s var(--ease);
}
.svc-panel--open .svc-panel__icon    { opacity: .85; transform: none; }
.svc-panel--open .svc-panel__no      { opacity: 1; transform: none; transition-delay: .05s; }
.svc-panel--open .svc-panel__title   { opacity: 1; transform: none; transition-delay: .13s; }
.svc-panel--open .svc-panel__desc    { opacity: 1; transform: none; transition-delay: .21s; }
.svc-panel--open .svc-panel__actions { opacity: 1; transform: none; transition-delay: .3s; }

.svc-panel__close {
    position: absolute;
    top: clamp(1.2rem, 2.4vh, 2rem);
    right: var(--gutter);
    width: clamp(48px, 3.6vw, 60px);
    aspect-ratio: 1;
    display: grid;
    place-items: center;
    border: 1px solid rgba(241, 239, 231, .7);
    border-radius: 50%;
    background: rgba(45, 32, 17, .55);
    color: #fff;
    cursor: pointer;
    opacity: 0;
    transition: opacity .5s var(--ease), background-color .4s var(--ease);
}
.svc-panel--open .svc-panel__close { opacity: 1; }
.svc-panel__close:hover { background: var(--ink); }
.svc-panel__close svg { width: 20px; height: 20px; }

/* =========================================================================
   CHAPTER â€” full-bleed photograph with the rust heading set over it
   ========================================================================= */

/* Iris chapter: a pinned entry. The stage is dark; the porthole layer is
   revealed through a growing circle (scrub.js writes --iris, the
   radial-gradient mask's stops read it) and a giant type train rolls
   horizontally through the open window, photographs riding it at their
   own drift. Defaults keep the window open without JS. */
.chapter--iris {
    height: calc(var(--scrub-vh, 460) * 1svh);
    /* overflow:hidden here would make this section the sticky stage's
       scroller and kill the pin; the stage clips itself. */
    overflow: visible;
}
.chapter__stage { background: var(--ink); }

/* Ground matches the section's data-ground, so no seam shows when the
   stage unpins over the transparent sections that follow. */
.chapter__porthole {
    position: absolute;
    inset: 0;
    background: var(--paper);
    -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 calc(var(--iris, 115) * 1%), transparent calc((var(--iris, 115) + 8) * 1%));
            mask-image: radial-gradient(circle at 50% 50%, #000 calc(var(--iris, 115) * 1%), transparent calc((var(--iris, 115) + 8) * 1%));
    overflow: hidden;
}

/* The train is the porthole's full content layer, so the roll keeps its
   viewport-height containing block no matter what the wrapper animates. */
.chapter__train {
    position: absolute;
    inset: 0;
}
.chapter__roll {
    position: absolute;
    top: 50%;
    left: 0;
    translate: 0 -50%;
    display: flex;
    align-items: center;
}
.chapter__word {
    font-family: var(--font-display);
    font-weight: 800;
    font-stretch: 125%;
    font-size: min(30vw, 68svh);
    line-height: 1;
    letter-spacing: .02em;
    white-space: nowrap;
    color: var(--ink);
}
.chapter__still {
    position: absolute;
    margin: 0;
    z-index: 2;
}
.chapter__still img {
    width: 100%;
    aspect-ratio: 16 / 10;
    object-fit: cover;
}
/* Two rows riding the train, echoing the reference's about strip: a top
   row and a bottom row, two frames already in the window and two waiting
   beyond the right edge to roll through. The offsets are measured from the
   roll box, whose height is the word's (min(30vw, 68svh)), so anchoring a
   row at Nsvh of the viewport means adding half that height back:
   top = Nsvh - 50svh + min(15vw, 34svh). */
.chapter__still--1 { width: min(26vw, 380px); left: 6vw;   top: calc(min(15vw, 34svh) - 37svh); }
.chapter__still--2 { width: min(22vw, 330px); left: 58vw;  bottom: calc(min(15vw, 34svh) - 43svh); }
.chapter__still--3 { width: min(24vw, 360px); left: 116vw; top: calc(min(15vw, 34svh) - 33svh); }
.chapter__still--4 { width: min(21vw, 320px); left: 174vw; bottom: calc(min(15vw, 34svh) - 40svh); }

/* =========================================================================
   KEYNOTE â€” one centred line given a whole quiet room
   ========================================================================= */

.keynote {
    display: grid;
    justify-items: center;
    text-align: center;
    /* Sits under a flush page-head: the top pad is the whole gap to the
       heading, and the larger bottom pad keeps the line belonging to the
       page above rather than the section below. */
    padding: clamp(2.5rem, 5vw, 5rem) var(--gutter) clamp(3.5rem, 7vw, 7rem);
}
.keynote__line {
    font-size: var(--t-lead);
    color: var(--ink);
    max-width: 40ch;
    margin: 0;
}
.keynote--quote { padding: 0 var(--gutter) clamp(4.5rem, 9vw, 9rem); }
.keynote--quote .keynote__line {
    font-family: var(--font-serif);
    font-style: italic;
    color: var(--ink-soft);
}

/* =========================================================================
   ABOUT — the opener and the founding story as one piece of writing. The
   writing holds one steady column while the photographs pass it at their
   own rates.
   ========================================================================= */

.about-drift {
    display: grid;
    grid-template-columns: minmax(0, 53fr) minmax(0, 43fr);
    gap: clamp(2.5rem, 6vw, 7rem);
    align-items: stretch;
    width: min(100% - var(--gutter) * 2, 78rem);
    margin-inline: auto;
    padding: clamp(6rem, 12vh, 9rem) 0 clamp(4.5rem, 9vw, 9rem);
}
.about-drift__eyebrow { color: var(--ink-faint); }
.about-drift__title {
    margin: .9rem 0 1.5rem;
    font-family: var(--font-serif);
    font-size: clamp(25px, 2.9vw, 42px);
    line-height: 1.14;
    color: var(--rust);
}
.about-drift__lead {
    margin: clamp(2.2rem, 4.5vw, 3.4rem) 0 1.3rem;
    font-family: var(--font-serif);
    font-size: clamp(19px, 1.8vw, 27px);
    line-height: 1.32;
    color: var(--ink);
}
.about-drift__text p {
    /* The grid column is the measure. A ch cap on top of it left the passage
       56px short of its own column at 1280 and 85px short at 1456. */
    max-width: none;
    margin: 0 0 1.2em;
    color: var(--ink);
}
/* The trailing margin would put the last line 20px above the last frame. */
.about-drift__text > :last-child { margin-bottom: 0; }
/* The writing sets the height of the section and the photographs fill it.
   In the flow the stack was always the taller of the two columns, and `1fr`
   rows cannot shrink below the pictures' own ratios while the grid height is
   still open, so the row took the photographs' height and the passage was
   left sitting above several hundred pixels of empty page. Out of flow it
   measures nothing, so the row is the writing and the stack takes exactly
   that. The crop is what makes the drift legible: each frame is wider than
   it is tall, so there is picture above and below the window to move. */
.about-drift__media {
    position: relative;
    min-height: 0;
}
.about-drift__plates {
    position: absolute;
    inset: 0;
    display: grid;
    grid-template-rows: repeat(3, minmax(0, 1fr));
    gap: clamp(1.5rem, 3vh, 2.5rem);
}
.about-drift__plate { margin: 0; overflow: hidden; height: 100%; }
.about-drift__plate img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    scale: 1.07;                                            /* absorbs the drift */
}

/* One column on a narrow screen: the photographs lead, the writing follows,
   and nothing is asked to sit beside anything. */
@media (max-width: 900px) {
    .about-drift {
        grid-template-columns: minmax(0, 1fr);
        padding-top: clamp(5rem, 11vh, 7rem);
    }
    /* One column: the stack returns to the flow and leads. */
    .about-drift__media { order: -1; }
    .about-drift__plates {
        position: static;
        grid-template-rows: none;
        gap: clamp(1rem, 3vw, 1.6rem);
    }
    .about-drift__plate { height: auto; }
    .about-drift__plate img { height: auto; aspect-ratio: 3 / 2; scale: 1; }
}

/* =========================================================================
   FOUNDER â€” portrait full bleed, name and bio set into the pale right
   half, sage pill
   ========================================================================= */

.founder {
    position: relative;
    min-height: 100svh;
    display: grid;
    align-items: center;
}
.founder__plate {
    position: absolute;
    inset: 0;
    margin: 0;
    overflow: hidden;
}
.founder__plate img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* Held to the right edge of the frame rather than near its left. The
       source has her against the left jamb with an empty pale corridor beside
       her, and that corridor is the ground the introduction is set on; anchor
       the other way and a taller section crops the corridor off first, which
       leaves the writing sitting on her shoulder. */
    object-position: 100% center;
    filter: grayscale(1);
}
.founder .sect-head {
    position: absolute;
    top: clamp(6.5rem, 14vh, 9rem);
    left: var(--gutter);
    z-index: 2;
    /* Smaller than the page heads, clearing the portrait. */
    font-size: clamp(19px, 1.5625vw, 30px);
}
.founder__body {
    position: relative;
    z-index: 2;
    grid-column: 1;
    justify-self: end;
    width: min(44vw, 620px);
    margin-right: var(--gutter);
    padding: clamp(1.5rem, 2.5vw, 2.5rem) 0;
}
.founder__name {
    font-family: var(--font-heading);
    font-weight: 400;
    font-size: var(--t-name);
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--rust);
    margin: 0 0 .9em;
}
.founder__body p { color: var(--ink); max-width: none; }
.founder__body .btn { margin-top: 1.4rem; }

/* Between these two widths the section is close enough to the photograph's own
   3:2 that cover has nothing left to trim sideways, so the frame holds her
   where she falls and the introduction lands on her shoulder. A little more
   height puts the box back out of that ratio, the trim returns, and the anchor
   above can carry her clear. Measured at the worst cell of a six by twenty
   grid down the column: 1366 goes 3.03 to 5.69, 1440 goes 4.48 to 4.98. Left
   off above 1600, where the section is already clear and this would only make
   it needlessly tall. */
@media (min-width: 1152px) and (max-width: 1599px) {
    .founder { min-height: max(100svh, 72vw); }
}

/* This section, alone on the site, changes shape earlier than the others.
   The introduction is set over the portrait and its length belongs to the
   client, so where the column is narrow the writing runs deep, the section
   outgrows the one screen it was drawn as, cover crops the picture harder and
   carries the figure under the words.
   Measured against the ink at sixteen bands down the column: at 1152 and above
   nothing falls below the readable threshold, and below it the worst band
   reaches 2.29 at 1024 and 1.63 at 901. So the photograph leads and the words
   follow it down the page from there, which is the same arrangement the phone
   has always had. */
@media (max-width: 1151px) {
    .founder { min-height: 0; display: block; background: var(--paper-pale); }
    .founder__plate { position: static; aspect-ratio: 3 / 2; }
    .founder__plate img { object-position: center 20%; }
    .founder .sect-head {
        position: static;
        display: block;
        padding: 2.5rem var(--gutter) 1.2rem;
    }
    .founder__body {
        width: auto;
        margin: 0;
        padding: 2rem var(--gutter) 3.5rem;
    }
}

/* =========================================================================
   JOURNAL INDEX â€” the entries given a page of their own: a photograph, the
   month it was written, the title and the line it opens with, one under
   another with a hairline and a lot of air between them
   ========================================================================= */

/* An editorial measure rather than the full window: the entries hold
   together as a column instead of stretching a title across a wide screen. */
.jindex {
    width: min(100% - var(--gutter) * 2, 74rem);
    margin-inline: auto;
    padding: clamp(2rem, 4vw, 4rem) 0 clamp(4.5rem, 9vw, 9rem);
}
.jindex__entry + .jindex__entry { border-top: 1px solid var(--rule); }
.jindex__link {
    display: grid;
    grid-template-columns: minmax(0, 26rem) minmax(0, 1fr);
    gap: clamp(1.6rem, 4vw, 4.5rem);
    align-items: center;
    padding: clamp(2.5rem, 5vw, 5rem) 0;
    text-decoration: none;
}
.jindex__link--plain { grid-template-columns: minmax(0, 1fr); }
.jindex__plate {
    margin: 0;
    aspect-ratio: 4 / 5;
    overflow: hidden;
}
.jindex__plate img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 1.2s var(--ease);
}
.jindex__body {
    display: grid;
    gap: clamp(.5rem, 1vw, .9rem);
    justify-items: start;
}
.jindex__date { color: var(--ink-faint); }
.jindex__title {
    font-family: var(--font-serif);
    font-weight: 500;
    font-size: clamp(24px, 2.4vw, 40px);
    line-height: 1.15;
    color: var(--ink);
    transition: color .5s var(--ease);
}
.jindex__standfirst {
    max-width: 46ch;
    margin: 0;
    color: var(--ink-soft);
}
.jindex__more {
    display: inline-flex;
    align-items: center;
    gap: .55rem;
    margin-top: clamp(.4rem, 1vw, .9rem);
    font-size: var(--t-caption);
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--ink-faint);
    transition: color .5s var(--ease);
}
.jindex__more svg { width: 20px; height: 20px; transition: transform .5s var(--ease); }
.jindex__link:hover .jindex__title { color: var(--rust); }
.jindex__link:hover .jindex__more { color: var(--ink); }
.jindex__link:hover .jindex__more svg { transform: translate3d(.35rem, 0, 0); }
.jindex__link:hover .jindex__plate img { transform: scale(1.03); }

.jindex__pagination {
    margin-top: clamp(1.5rem, 3vw, 3rem);
    font-size: var(--t-caption);
    letter-spacing: .1em;
}
.jindex__pagination:empty { display: none; }
.jindex__pagination a,
.jindex__pagination span { padding: 0 .5em; text-decoration: none; }
.jindex__empty { color: var(--ink-soft); margin-top: 1.5rem; }

/* =========================================================================
   JOURNAL PEEK â€” the landing's glance at the same material: three upright
   photographs with a date and a title under each. No page heading, no
   standfirst, no rules; the index is where an entry is properly introduced
   ========================================================================= */

.jpeek {
    padding: clamp(4rem, 8vw, 8rem) var(--gutter) clamp(4.5rem, 9vw, 9rem);
}
.jpeek__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: clamp(1.6rem, 3vw, 2.6rem);
}
/* Named the section it opens, so it is a heading now. The house rule sets
   headings to 1.15, which would close this line up by 6px and lift the whole
   section with it; the label keeps the body's own leading. */
.jpeek__eyebrow { margin: 0; line-height: 1.7; color: var(--ink-faint); }
/* Small landscape thumbnails held to the left, against the index's upright
   feature plates: the two sections must not read as the same layout twice. */
.jpeek__row {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 20rem));
    justify-content: start;
    gap: clamp(1.2rem, 2.4vw, 2.4rem);
}
.jpeek__card {
    display: grid;
    gap: .5rem;
    justify-items: start;
    text-decoration: none;
}
.jpeek__plate {
    width: 100%;
    margin: 0 0 .35rem;
    aspect-ratio: 3 / 2;
    overflow: hidden;
}
.jpeek__plate img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 1.2s var(--ease);
}
.jpeek__date {
    font-size: var(--t-caption);
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--ink-faint);
}
.jpeek__title {
    font-weight: 300;
    font-size: var(--t-lead);
    line-height: 1.35;
    color: var(--ink);
    transition: color .5s var(--ease);
}
.jpeek__card:hover .jpeek__title { color: var(--rust); }
.jpeek__card:hover .jpeek__plate img { transform: scale(1.03); }

/* One entry, set as a centred composition so the landing closes on a
   balanced note. The index page keeps the left-aligned editorial rows. */
.jpeek--single {
    display: grid;
    justify-items: center;
    text-align: center;
}
.jpeek--single .jpeek__eyebrow { margin-bottom: clamp(1.4rem, 3vw, 2.4rem); }
.jpeek--single .jpeek__feature {
    display: grid;
    justify-items: center;
    gap: .55rem;
    width: min(100%, 52rem);
    text-decoration: none;
}
.jpeek--single .jpeek__plate {
    width: 100%;
    margin: 0 0 clamp(1.2rem, 2.5vw, 2rem);
    aspect-ratio: 3 / 2;
}
.jpeek--single .jpeek__plate > span { display: block; width: 100%; height: 100%; }
.jpeek--single .jpeek__title {
    font-family: var(--font-serif);
    font-weight: 500;
    font-size: clamp(20px, 1.9vw, 30px);
    line-height: 1.2;
}
.jpeek--single .jpeek__feature:hover .jpeek__title { color: var(--rust); }
.jpeek--single .jpeek__feature:hover .jpeek__plate img { transform: scale(1.03); }
.jpeek--single .jpeek__all { margin-top: clamp(1.4rem, 3vw, 2.2rem); }

/* â”€â”€ Journal entry â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */

/* =========================================================================
   JOURNAL ENTRY â€” a long read: the writing held to a readable measure in the
   middle of the page, her photographs and film breaking out of that column
   at the passages they belong to
   ========================================================================= */

.entry__portrait { margin: 0; }
.entry__titling { align-self: center; }
.entry__date { color: var(--ink-faint); }
.entry__title { margin-top: .55rem; }
.entry__standfirst {
    max-width: 44ch;
    margin-top: clamp(1rem, 2vw, 1.5rem);
    font-size: var(--t-lead);
    line-height: 1.6;
    color: var(--ink-soft);
}

/* Nothing is cropped to a house shape: an upright photograph stays upright,
   a landscape film stays landscape, and the layout gives each the width its
   own proportion asks for. */
/* The frame carries a ground of its own, so a picture still on the wire reads
   as an empty plate rather than a hole in the page. It sits on the element the
   wipe clips, never on its parent: a tone on the parent would show as a panel
   beside the sweep. */
.entry__frame {
    display: block;
    overflow: hidden;
    background: var(--paper-deep);
}
.entry__frame img,
.entry__frame video {
    display: block;
    width: 100%;
    height: auto;
}

/* Opening: her photograph beside the title rather than a banner over it. */
.entry__opening {
    display: grid;
    grid-template-columns: minmax(0, 27rem) minmax(0, 1fr);
    gap: clamp(2rem, 5vw, 5rem);
    align-items: center;
    width: min(100% - var(--gutter) * 2, 74rem);
    margin-inline: auto;
    padding: clamp(6rem, 12vh, 8.5rem) 0 clamp(2.5rem, 5vw, 4.5rem);
}
/* An entry published without a photograph opens on its title alone rather
   than on a title pushed into the second column of an empty grid. */
/* The title sits on the same axis as the column beneath it: centred, so the
   head of the page is symmetrical with the document it opens. */
.entry__opening--plain {
    grid-template-columns: minmax(0, 1fr);
    width: min(100% - var(--gutter) * 2, 58rem);
    text-align: center;
}
/* The label carries a max-width of its own, so centring its text is not
   enough: the block has to be centred too. */
.entry__opening--plain .entry__title,
.entry__opening--plain .entry__date { margin-inline: auto; }
/* With a film above it the title needs no runway of its own. */
.entry__cover + .entry__opening { padding-top: clamp(2.2rem, 4.5vw, 3.6rem); }

.entry__body { padding-bottom: clamp(3rem, 6vw, 6rem); }

/* One document, one measure. Every paragraph and every picture takes the
   same column and the same air, at every width: the client reads an entry as
   pages of a document, so nothing is set beside anything and no passage is
   left stranded next to a taller picture. */
.entry__doc {
    width: min(100% - var(--gutter) * 2, 46rem);
    margin-inline: auto;
}
.entry__body p {
    max-width: none;
    margin: 0 0 1.25em;
    font-size: clamp(16px, 1.05vw, 19px);
    line-height: 1.85;
}
.entry__body p strong { font-weight: 500; color: var(--ink); }

/* Equal air above and below, and never taller than a reader can take in at
   once. An upright picture given the full measure stands over 1300px high and
   swamps the passage it belongs to, so height is capped and the figure shrinks
   to whatever the picture actually occupies. Sizing the FIGURE rather than a
   frame inside it keeps the caption on the edge of the picture. */
.entry__figure {
    width: fit-content;
    max-width: 100%;
    margin: clamp(2.4rem, 5vw, 3.6rem) auto;
}
.entry__figure .entry__frame {
    display: block;
    overflow: hidden;
    background: var(--paper-deep);
}
.entry__figure img,
.entry__figure video {
    display: block;
    /* fit-content measures the widest child, so a caption longer than a
       height-capped upright picture would widen the figure and leave the
       picture against its left edge while the caption stayed centred. She
       writes her own captions, so the picture holds the axis either way. */
    margin-inline: auto;
    width: auto;
    height: auto;
    max-width: 100%;
    max-height: clamp(22rem, 66vh, 40rem);
}

/* Two photographs on one line. Each figure grows by its own width over height,
   so a row of two resolves to a single height and the widths follow the
   pictures. The image fills its share, since the share is already its shape;
   the house height cap is lifted here because width is what binds. */
.entry__pair {
    display: flex;
    align-items: flex-start;
    justify-content: center;
    gap: clamp(.7rem, 1.6vw, 1.2rem);
    margin: clamp(2.4rem, 5vw, 3.6rem) auto;
}
.entry__pair .entry__figure {
    flex: var(--ar, 1) 1 0;
    width: auto;
    min-width: 0;
    margin: 0;
}
.entry__pair .entry__figure img,
.entry__pair .entry__figure video {
    width: 100%;
    height: auto;
    max-height: none;
}

/* The film the piece opens on, before its title, across the whole page. */
.entry__cover {
    margin: 0;
    background: var(--paper-deep);
    overflow: hidden;
}
.entry__cover .entry__frame { display: block; }
.entry__cover img,
.entry__cover video {
    display: block;
    width: 100%;
    height: auto;
}

/* Under the picture and on its axis, so a centred plate reads as one object
   with its line rather than as a picture with a label hung off one corner. */
.entry__figure .entry__caption { text-align: center; }
.entry__caption {
    margin-top: .75rem;
    font-size: var(--t-caption);
    letter-spacing: .1em;
    color: var(--ink-faint);
}

.page-plate {
    margin: 0 var(--gutter) clamp(2.5rem, 5vw, 5rem);
    overflow: hidden;
}
.page-plate span { display: block; }
/* Overscan must beat the parallax drift at the smallest supported viewport:
   height x (scale-1)/2 >= drift x scale (scale composes after transform).
   1.12 covers the 8px drift the templates set, down to 320px wide. */
.page-plate img { width: 100%; height: auto; scale: 1.12; }


.prose { padding: 0 var(--gutter) clamp(4rem, 8vw, 8rem); }
.prose > * { max-width: 58ch; }
.prose p {
    line-height: 1.8;
    color: var(--ink);
    margin: 0 0 1.4em;
}
.prose h2, .prose h3 { margin: 1.8em 0 .6em; font-weight: 400; color: var(--rust); }
.prose img { margin: 2em 0; }
.prose a { color: var(--rust); }

.closing--entry { padding: 0 var(--gutter) clamp(4rem, 8vw, 8rem); }

/* =========================================================================
   CONTACT â€” heading on beige, hairline fields, sage pill submit
   ========================================================================= */

.contact {
    display: grid;
    grid-template-columns: minmax(16rem, 30rem) 1fr;
    gap: clamp(2.5rem, 6vw, 7rem);
    padding: clamp(1rem, 2vw, 2rem) var(--gutter) clamp(4.5rem, 9vw, 9rem);
}
/* Following a photographic cover instead of a page head. */
.contact--pad { padding-top: clamp(3.5rem, 7vw, 7rem); }
/* The measure here is the aside column itself, which is already capped at
   30rem. A second cap in ch sat 38px short of the column on a phone and 467px
   short on a tablet, so the paragraph stopped while the form rules beside it
   ran on, and the block read as broken padding rather than as a measure. */
.contact__note { color: var(--ink); max-width: none; margin: 0 0 1.4rem; }
.contact__label { color: var(--ink-soft); margin: 2rem 0 .4rem; }
.contact__mail { margin: 0; }

.contact__main { max-width: 40rem; }

.notice {
    max-width: none;
    padding: 1rem 1.2rem;
    margin: 0 0 2rem;
}
.notice--ok  { background: var(--sage); color: var(--ink); }
.notice--err { background: #E4CFC5; color: var(--rust); }

.form__hp { position: absolute; left: -9999px; }

.form__optional {
    text-transform: none;
    letter-spacing: normal;
    color: var(--ink-faint);
}

.form__row {
    max-width: none;
    display: grid;
    gap: .5rem;
    margin: 0 0 1.8rem;
}
.form__row label {
    font-size: 12px;
    letter-spacing: .18em;
    text-transform: uppercase;
    color: var(--ink-soft);
}
.form__row input,
.form__row textarea {
    font-family: var(--font-body);
    font-weight: 300;
    font-size: var(--t-body);
    color: var(--ink);
    background: transparent;
    border: 0;
    border-bottom: 1px solid var(--rule);
    padding: .6rem 0;
    border-radius: 0;
    transition: border-color .4s var(--ease);
}
/* No outline:none here â€” the :where() focus-visible rule at the top of the
   file supplies the rust outline for keyboard users; mouse focus only moves
   the hairline. */
.form__row input:focus,
.form__row textarea:focus {
    border-color: var(--ink);
}
.form__row textarea { resize: vertical; }

/* =========================================================================
   SOCIAL â€” the two marks, shared by the footer band and the contact page
   ========================================================================= */

.social {
    list-style: none;
    display: flex;
    align-items: center;
    gap: 1.15rem;
    margin: 0;
    padding: 0;
}
.social__mark {
    display: grid;
    place-items: center;
    width: 22px;
    height: 22px;
    color: inherit;
    opacity: .6;
    transition: opacity .4s var(--ease);
}
a.social__mark:hover,
a.social__mark:focus-visible { opacity: 1; }
.social__mark--instagram svg { width: 19px; height: 19px; }
/* The filled glyph carries more ink than the outlined one at equal size. */
.social__mark--linkedin svg { width: 18px; height: 18px; }

/* Under its own label, the way the email address sits under "Email". */
.social--contact { margin-top: .1rem; }

/* =========================================================================
   COLOPHON â€” a centred close, the way the client's tone reference ends its
   pages: the wordmark, one line of purpose, the ways in, the hairline
   ========================================================================= */

.colophon {
    background: var(--ink);
    color: var(--paper-pale);
    padding: clamp(2.2rem, 4vw, 3.5rem) var(--gutter) clamp(1.4rem, 2.5vw, 2rem);
}
.colophon__row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: clamp(1.2rem, 3vw, 3rem);
    flex-wrap: wrap;
}
.colophon__mark {
    font-family: var(--font-display);
    font-weight: 800;
    font-stretch: 125%;
    font-size: 14px;
    letter-spacing: .12em;
    color: var(--paper-pale);
    text-decoration: none;
    transition: color .4s var(--ease);
}
.colophon__mark:hover { color: #fff; }
.colophon__nav {
    display: flex;
    flex-wrap: wrap;
    gap: clamp(1.2rem, 2.6vw, 2.4rem);
}
.colophon__nav a,
.colophon__mail {
    font-size: 11.5px;
    font-weight: 400;
    letter-spacing: .2em;
    text-transform: uppercase;
    text-decoration: none;
    color: var(--paper-pale);
    background: linear-gradient(currentColor, currentColor) left bottom / 0 1px no-repeat;
    padding-bottom: 3px;
    transition: background-size .45s var(--ease), color .4s var(--ease);
}
.colophon__nav a:hover,
.colophon__mail:hover { color: #fff; background-size: 100% 1px; }
.colophon__mail { color: #B9AE9D; }

/* Three tracks so the marks hold the centre of the band whatever the two
   lines of small print measure. */
.colophon__base {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 1rem 2rem;
    border-top: 1px solid rgba(241, 239, 231, .14);
    margin-top: clamp(1.8rem, 3vw, 2.6rem);
    padding-top: clamp(1rem, 1.8vw, 1.4rem);
}
.colophon__base .social { color: var(--paper-pale); }
.colophon__tag { justify-self: end; text-align: right; }

@media (max-width: 700px) {
    .colophon__base {
        grid-template-columns: 1fr;
        justify-items: center;
        gap: .9rem;
    }
    .colophon__base .social { order: -1; }
    .colophon__tag { justify-self: center; text-align: center; }
}
.colophon__legal,
.colophon__tag {
    color: #8D8271;
    font-size: 11px;
    letter-spacing: .14em;
    max-width: none;
    margin: 0;
}

/* =========================================================================
   PAGE VEIL â€” kept deviation: the paper wash between pages. The first-load
   mark was removed at the client's request; nothing now stands in front of
   the page on arrival
   ========================================================================= */

.veil {
    position: fixed;
    inset: 0;
    z-index: 80;
    background: var(--paper);
    opacity: 0;
    pointer-events: none;
    transition: opacity .24s ease;
}
.is-leaving .veil { opacity: 1; pointer-events: auto; }

html:not(.is-loading) main { animation: page-in .7s var(--ease) both; }
@keyframes page-in { from { opacity: 0; } to { opacity: 1; } }

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

@media (prefers-reduced-motion: reduce) {
    .reveal-on-scroll {
        opacity: 1 !important;
        transform: none !important;
        clip-path: none !important;
    }
    .reveal-on-scroll[data-reveal="mask"] > * { clip-path: none !important; }
    .reveal-on-scroll[data-reveal="lines"] .line__inner {
        transform: none !important;
        transition: none !important;
    }
    .veil { display: none; }
    .svc-panel { display: none; }
    html:not(.is-loading) main { animation: none; }
    .cover__plate img,
    .cover__plate video,
    .page-cover__plate img { animation: none; }
    .page-plate img { scale: none; }
    .masthead { transition: none; }
    *, *::before, *::after {
        animation-duration: .01ms !important;
        transition-duration: .01ms !important;
    }
}

/* =========================================================================
   MOBILE
   ========================================================================= */

@media (max-width: 900px) {
    /* The plate leads the list instead of standing beside it; with no
       pointer to follow it simply holds the first service. */
    .svcindex { grid-template-columns: 1fr; gap: clamp(1.8rem, 4vw, 3rem); }
    .svcindex__plate { aspect-ratio: 16 / 11; }

    .statement { grid-template-columns: 1fr; min-height: 0; }
    .statement__plate { aspect-ratio: 4 / 3; }
    .statement__plate img { position: static; height: 100%; }

    /* The riding stills keep the reference's mobile presence (~30svh). */
    .chapter__still--1 { width: 52vw; }
    .chapter__still--2 { width: 46vw; }
    .chapter__still--3 { width: 48vw; }
    .chapter__still--4 { width: 44vw; }

    .contact { grid-template-columns: 1fr; }

    /* The photograph leads and the words follow, so an entry still arrives
       whole on one screen. */
    .jindex__link { grid-template-columns: 1fr; gap: clamp(1rem, 3vw, 1.6rem); }
    .jindex__plate { aspect-ratio: 3 / 2; }

    /* The entry opens as one column: photograph, then the title under it. */
    .entry__opening {
        grid-template-columns: 1fr;
        gap: clamp(1.4rem, 4vw, 2.4rem);
        padding-top: clamp(5rem, 11vh, 7rem);
    }
    .entry__opening .entry__frame { width: min(100%, 26rem); }
}

@media (max-width: 700px) {
    /* A glance should stay a glance: the three cards lie down as rows rather
       than becoming a second column of full-height cards. */
    .jpeek__row { grid-template-columns: 1fr; gap: clamp(1rem, 3vw, 1.4rem); }
    .jpeek__card {
        grid-template-columns: 92px minmax(0, 1fr);
        align-items: center;
        gap: 0 1rem;
    }
    .jpeek__plate { grid-row: span 2; margin: 0; aspect-ratio: 1; }
    .jpeek__date { align-self: end; }
    .jpeek__title { align-self: start; }
}

@media (max-width: 700px) {
    /* The longest names need the full row here. */
    .svcindex__row a { grid-template-columns: 2rem minmax(0, 1fr) auto; }
    .svcindex__title { font-size: clamp(17px, 4.4vw, 22px); }

    /* Half a phone is not a photograph. The pair lies down and each picture
       takes the column in turn, at the size every other one has. */
    .entry__pair { display: block; }
    .entry__pair .entry__figure {
        width: fit-content;
        max-width: 100%;
        margin-inline: auto;
    }
    .entry__pair .entry__figure + .entry__figure { margin-top: clamp(2.4rem, 5vw, 3.6rem); }
    .entry__pair .entry__figure img,
    .entry__pair .entry__figure video {
        width: auto;
        max-width: 100%;
        max-height: clamp(22rem, 66vh, 40rem);
    }
}

@media (max-width: 640px) {
    .gallery__card { width: min(78vw, 340px); }
}

/* Tablets included: an iPad landscape is still a touch layout. */
@media (max-width: 1024px) {
    html.js .masthead__nav { display: none; }
    html.js .masthead__burger { display: block; }

    /* Overlaid arrows sit awkwardly under a thumb; park them together
       below the rail instead, in room added under the rail's own. */
    .gallery { padding-bottom: calc(clamp(3rem, 6vw, 6rem) + 72px); }
    .gallery__btn { top: auto; bottom: clamp(3rem, 6vw, 6rem); transform: none; }
    .gallery__btn--prev { left: auto; right: calc(var(--gutter) + 64px); }
    .gallery__btn--next { right: var(--gutter); }
}

@media (max-width: 600px) {
    /* Without JS the plain row still renders; keep it wearable. */
    html:not(.js) .masthead {
        flex-direction: column;
        align-items: flex-start;
        gap: .55rem;
        padding: .9rem 1.25rem;
    }
    .masthead__nav { gap: 1rem; }
    .masthead__nav a { font-size: 10px; letter-spacing: .14em; }
}

/* â”€â”€ No JavaScript: everything is simply visible â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */

html:not(.js) .reveal-on-scroll { opacity: 1 !important; transform: none !important; }
html:not(.js) .reveal-on-scroll[data-reveal="mask"] > * { clip-path: none !important; }
html:not(.js) .line__inner { transform: none !important; }
/* The rail scrolls natively on its own; only the arrows are script, and
   the description each card would have opened into comes back onto it. */
html:not(.js) .gallery__btn { display: none; }
/* No arrows to park under the rail on a touch layout, so the extra room
   the tablet rule adds for them goes back. */
html:not(.js) .gallery { padding-bottom: clamp(3rem, 6vw, 6rem); }
html:not(.js) .gallery__desc {
    display: block;
    margin-top: .5rem;
    color: rgba(255, 255, 255, .92);
    font-size: 13px;
    line-height: 1.6;
    text-shadow: 0 1px 24px rgba(0, 0, 0, .5);
}

/* The window's photo wipes only exist as scrub choreography. */
html:not(.js) .cover__frames { display: none; }
