/* ==========================================================================
   Virginia Maggio Art: stylesheet
   Built to The Picsel Design Standard v1.0.

   The one sentence version of the design: the paintings hang on a wall, and
   the words sit on paper beside it. Two grounds, never one flat cream. Work
   goes on --wall, which has plaster tooth and a real cast shadow under every
   painting. Reading goes on --paper, which is clean and quiet. Every other
   decision below serves that split.
   ========================================================================== */

/* --------------------------------------------------------------------------
   FONTS
   Self-hosted, one variable file per family, latin subset only.

   Cormorant Garamond for headings and painting titles: a tall, fine old
   style serif with long ascenders, which gives the name and the titles the
   feel of a label on a gallery wall. Karla for everything else: plain,
   slightly quirky and easy to read on a phone. Virginia chose this pair
   herself (option 5 of six) on 5 October 2026.

   The fallback numbers were read out of the head, hhea and OS/2 tables of
   these exact files, so the fallback takes up the same space as the real
   face and the swap causes no measurable layout shift.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Karla";
  font-style: normal;
  font-weight: 200 800;
  font-display: swap;
  src: url("/fonts/karla-latin-var.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("/fonts/cormorant-garamond-latin-var.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Only the quote band uses the italic, so it is never preloaded. */
@font-face {
  font-family: "Cormorant Garamond";
  font-style: italic;
  font-weight: 300 700;
  font-display: swap;
  src: url("/fonts/cormorant-garamond-latin-italic-var.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Karla Fallback";
  src: local("Arial"), local("Helvetica Neue"), local("Liberation Sans");
  size-adjust: 96.9%;
  ascent-override: 94.6%;
  descent-override: 26%;
  line-gap-override: 0%;
}

@font-face {
  font-family: "Cormorant Garamond Fallback";
  src: local("Georgia"), local("Times New Roman"), local("Liberation Serif");
  size-adjust: 89.7%;
  ascent-override: 103%;
  descent-override: 32%;
  line-gap-override: 0%;
}

/* --------------------------------------------------------------------------
   TOKENS
   Nothing below this block hardcodes a colour, size, space or duration.
   -------------------------------------------------------------------------- */

:root {
  /* --- type roles ---------------------------------------------------------
     Two faces, jobs written down. Karla: body, UI and the small
     uppercase labels. Cormorant Garamond: headings, painting titles, prices, the
     quote. Swap either here and nowhere else. */
  --font-body: "Karla", "Karla Fallback", system-ui, sans-serif;
  --font-display: "Cormorant Garamond", "Cormorant Garamond Fallback", Georgia, serif;

  /* --- space --------------------------------------------------------------
     Ten steps, each at least 25% larger than the last so no two are ever
     ambiguous. Do not add new spacers; combine these. */
  --space-1: 0.25rem;   /*   4px  icon to label                            */
  --space-2: 0.5rem;    /*   8px  tight inline gaps                        */
  --space-3: 0.75rem;   /*  12px  form control inset                       */
  --space-4: 1rem;      /*  16px  card inset, paragraph gap                */
  --space-5: 1.5rem;    /*  24px  comfortable inset, grid gutter           */
  --space-6: 2rem;      /*  32px  between components in a group            */
  --space-7: 3rem;      /*  48px  between groups                           */
  --space-8: 4rem;      /*  64px  sub-section separation                   */
  --space-9: 6rem;      /*  96px  section padding, desktop, tight          */
  --space-10: 7.5rem;   /* 120px  section padding, desktop, generous       */

  /* --- section rhythm -----------------------------------------------------
     Three, not one. Every section at the same padding is the flattest tell
     of template work there is; the page needs somewhere to breathe in and
     somewhere to breathe out. */
  --section-tight: clamp(3rem, 6vw, 5rem);      /* 48 -> 80  */
  --section-base: clamp(3.5rem, 8vw, 7.5rem);   /* 56 -> 120 */
  --section-loose: clamp(4.5rem, 11vw, 10rem);  /* 72 -> 160 */
  --page-gutter: clamp(1.5rem, 5vw, 8rem);

  /* --- type scale ---------------------------------------------------------
     Fluid 360 -> 1280. Every max is under 2.5x its min, so text still scales
     to 200% on zoom without the clamp fighting the user. */
  --step--1: clamp(0.8854rem, 0.8601rem + 0.1123vw, 0.95rem);
  --step-0: clamp(1.0625rem, 1.0136rem + 0.2174vw, 1.1875rem);
  --step-1: clamp(1.275rem, 1.1931rem + 0.3641vw, 1.4844rem);
  --step-2: clamp(1.53rem, 1.4026rem + 0.566vw, 1.8555rem);
  --step-3: clamp(1.836rem, 1.6469rem + 0.8406vw, 2.3193rem);
  --step-4: clamp(2.2032rem, 1.9309rem + 1.2104vw, 2.8992rem);
  --step-5: clamp(2.6438rem, 2.2603rem + 1.7046vw, 3.624rem);

  --lh-tight: 1.05;
  --lh-heading: 1.15;
  --lh-ui: 1.25;
  --lh-body: 1.6;

  /* The wide uppercase tracking is this site's inherited voice and it stays.
     What changed is that it is now three values with jobs, rather than
     eleven values scattered between 0.04em and 0.2em. */
  --track-body: 0.01em;
  --track-caps: 0.12em;
  --track-caps-wide: 0.18em;

  --measure-prose: 68ch;
  --measure-lead: 58ch;
  --measure-head: 20ch;

  /* --- containers ---------------------------------------------------------
     Three widths, alternated deliberately, so body copy never runs to 130
     characters just because the hero needed to be wide. */
  --container-prose: 42rem;
  --container-content: 64rem;
  --container-wide: 80rem;

  /* --- colour -------------------------------------------------------------
     The starting palette, as roles. Virginia's accent replaces it once her
     photos are in and Ben has picked from two options (plan, section 8).
     Contrast measured rather than eyeballed:
       --ink-muted  on --paper   4.97:1   (body minimum is 4.5)
       --wall-muted on --wall    5.81:1
       --ink        on --paper  15.60:1
     Neither ground is pure white; neither text colour is pure black. */
  --paper: #f7f6f3;
  --paper-tint: #fbfaf7;
  --wall: #eae5dc;
  --wall-deep: #ded8cc;
  --ink: #1f1f1f;
  --ink-muted: #6e6a64;
  --wall-muted: #5a564f;
  --line: #e4e1dc;
  --line-strong: #c9c4ba;

  /* The one accent: the shocking pink Virginia paints her grounds with
     before anything else goes on, so a wee bit of it shows through the
     finished painting. Here it shows through the site the same way, in
     small amounts: hovers, the active nav, focus rings, the quote marks
     and the small labels. Never a short decorative rule under a heading. Never on or over a
     painting, and never as a block of colour.
       --pink      fills only. --ink on it is 4.2:1, fine at band size
       --pink-ink  text and lines on --paper or --wall, 5.8:1 on --paper */
  --pink: #ec3a83;
  --pink-ink: #b8185a;
  /* For small text on the dark footer, where --pink falls just under 4.5:1. */
  --pink-on-dark: #f472a6;

  /* Kept as aliases: .bg-offwhite and .text-charcoal are in the markup of
     every generated page, and renaming them buys nothing. */
  --offwhite: var(--paper);
  --charcoal: var(--ink);
  --muted: var(--ink-muted);

  /* --- depth --------------------------------------------------------------
     One light source for the whole site: high and slightly to the left, so
     every vertical offset is roughly twice its horizontal one. Tinted to the
     putty, never pure black. Elevation raises offset and blur while lowering
     opacity, which is what stops a large shadow reading as a smudge. */
  --shadow-hsl: 36deg 20% 26%;

  --shadow-sm: 0.5px 1px 1px hsl(var(--shadow-hsl) / 0.28);

  --shadow-md:
    1px 2px 2px hsl(var(--shadow-hsl) / 0.16),
    2px 4px 4px hsl(var(--shadow-hsl) / 0.16),
    3px 6px 6px hsl(var(--shadow-hsl) / 0.16);

  /* The one that does the signature work: a painting hung on a wall. */
  --shadow-hung:
    1px 2px 2px hsl(var(--shadow-hsl) / 0.13),
    2px 4px 4px hsl(var(--shadow-hsl) / 0.13),
    4px 8px 8px hsl(var(--shadow-hsl) / 0.13),
    8px 16px 16px hsl(var(--shadow-hsl) / 0.11);

  /* Square corners are a decision, not a default. This is a gallery, and the
     paintings, the cards and the buttons all share one hard edge. */
  --radius-base: 0;
  --radius-pill: 999px;

  /* --- motion -------------------------------------------------------------
     Everything decelerates; nothing uses the browser default `ease`, which
     accelerates into its end and reads as sluggish on interface work. */
  --ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
  --ease-out-cubic: cubic-bezier(0.33, 1, 0.68, 1);
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);

  --dur-instant: 100ms;
  --dur-fast: 150ms;
  --dur-base: 250ms;
  --dur-entrance: 400ms;

  /* --- chrome -------------------------------------------------------------
     The sticky navbar's exact height. The navbar is held to it, and the hero
     fills the screen below it, so the two always add up to one screen. */
  --nav-h: 5.5rem;
}

@media (max-width: 991px) {
  :root {
    /* The navbar collapses to the wordmark and the toggle here, the same
       breakpoint as Bootstrap's navbar-expand-lg. */
    --nav-h: 4rem;
  }
}
/* ==========================================================================
   BASE
   The quality floor, applied once. Nothing here is optional.
   ========================================================================== */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  font-size: 100%;
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%; /* stops iOS landscape zoom */
}

body {
  margin: 0;
  background-color: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: var(--lh-body);
  letter-spacing: var(--track-body);
  font-optical-sizing: auto;
}

/* Body copy was 1.7 across the site. That is a reading line-height for a
   672px measure of long-form prose; at these measures it opens the lines far
   enough that the paragraph stops reading as a block. 1.6 on desktop, and
   tighter still on a phone where the measure is short. */
@media (max-width: 640px) {
  body {
    line-height: 1.45;
  }
}

body.is-loading,
body.is-modal-open {
  overflow: hidden;
}

/* Available to a screen reader, absent from the page. Used where a visible
   label is short enough to be ambiguous out of context - "Enquire" is clear
   beside its painting and useless in a list of sixteen links. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- headings -------------------------------------------------------------
   Cormorant Garamond takes every heading. It has no optical size axis and
   its hairlines are fine, so check small titles on a phone before thinning
   anything further. */
h1,
h2,
h3,
h4,
.font-display {
  font-family: var(--font-display);
  font-weight: 400;
  text-wrap: balance;
  color: var(--ink);
}

p,
li {
  text-wrap: pretty;
}

/* --- links ----------------------------------------------------------------
   The house link: a border that colours in, never a default underline that
   toggles. Declared once so the six near-identical copies of this rule that
   used to exist can go. */
.link-quiet {
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition:
    border-color var(--dur-fast) var(--ease-out-quad),
    color var(--dur-fast) var(--ease-out-quad);
}

.link-quiet:hover,
.link-quiet:focus-visible {
  color: var(--ink-muted);
  border-color: var(--ink-muted);
}

/* --- the caps voice -------------------------------------------------------
   Uppercase with wide tracking is the house idiom and it is kept. The
   floor it now has: never below --step--1, which is 14.2px at 360 and 15.2px
   at 1280. Several of these labels were 0.72rem, or 11.5px, which is below
   what anyone should be asked to read. */
.caps {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--ink-muted);
}

.tabular {
  font-variant-numeric: tabular-nums; /* prices */
}

img,
video {
  max-width: 100%;
  height: auto;
  display: block;
}

/* --------------------------------------------------------------------------
   THE WALL
   The signature move. Any section carrying work gets the putty ground, the
   plaster tooth, and a light that falls from high and slightly left - the
   same direction as every shadow in --shadow-hsl. Any section carrying
   reading stays on paper.
   -------------------------------------------------------------------------- */

.wall {
  position: relative;
  isolation: isolate;
  background-color: var(--wall);
  color: var(--ink);
  /* The quote band breaks out to the full width of the viewport from inside
     the container. `clip` rather than `hidden` so this does not become a
     scroll container and break the sticky navbar above it. */
  overflow-x: clip;
}

/* The light. A single wide, soft pool falling from upper left, so the wall is
   never one flat fill.

   It is sized in pixels rather than as a percentage of the element, and that
   is the whole point: a wall may be one screen tall or six, and a percentage
   gradient would stretch the same falloff over the entire run, so a long
   wall would read as a slow vertical ramp instead of as light from a window.
   Pinned to the top at a fixed height, the light stays light and the rest of
   the wall stays wall. */
.wall::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  background-color: var(--wall);
  background-image: radial-gradient(
    130% 100% at 22% 0%,
    hsl(40deg 30% 98% / 0.6) 0%,
    hsl(40deg 30% 98% / 0) 72%
  );
  background-repeat: no-repeat;
  background-position: top center;
  background-size: 100% 900px;
}

/* The tooth. A 128px tile, baked once by `npm run texture` and committed, in
   preference to running an SVG turbulence filter on every paint - that
   filter is a per-frame cost on a page whose whole job is showing large
   images. Greyscale under `overlay` means the one tile works over the wall,
   over paper, and over a photograph. */
.wall::after,
.grain::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: url("/images/texture/plaster-128.png");
  background-size: 128px 128px;
  mix-blend-mode: overlay;
  opacity: 0.42;
}

@media (prefers-reduced-transparency: reduce) {
  .wall::after,
  .grain::after {
    display: none;
  }
}

.grain {
  position: relative;
  isolation: isolate;
}

/* A painting hung on the wall. The shadow is the whole point: it is what
   makes the difference between an image sitting on a page and an object
   hanging in a room. */
.hung {
  box-shadow: var(--shadow-hung);
}

/* --------------------------------------------------------------------------
   LAYOUT PRIMITIVES
   Components own their padding. Layouts own the gaps between things. No
   component below carries an outer margin.
   -------------------------------------------------------------------------- */

.container-xl {
  width: min(var(--container-wide), 100%);
  padding-left: var(--page-gutter);
  padding-right: var(--page-gutter);
  margin-inline: auto;
}

.section-space {
  padding-block: var(--section-base);
}

.section-space--tight {
  padding-block: var(--section-tight);
}

.section-space--loose {
  padding-block: var(--section-loose);
}

.section-tint {
  background-color: var(--paper-tint);
}

/* --------------------------------------------------------------------------
   INTERACTION FLOOR
   All three states on everything clickable, and one focus ring for the whole
   site. Bootstrap ships its own ring in its own blue, which has nothing to
   do with this palette; this overrides it everywhere.
   -------------------------------------------------------------------------- */

:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--pink-ink);
  outline-offset: 3px;
  border-radius: var(--radius-base);
}

/* Bootstrap sets its ring with box-shadow, so outline alone does not remove
   it. On the wall the ink ring would sit too heavy against the putty, so it
   lightens to the wall's own text colour. */
.btn:focus,
.btn:focus-visible,
.form-control:focus,
.form-select:focus,
.accordion-button:focus {
  box-shadow: none;
}

.wall :where(a, button, select, summary, [tabindex]):focus-visible {
  outline-color: var(--pink-ink);
}

/* Every clickable thing gets a pressed state. Its absence is the single
   cheapest tell that an interface was never actually used by the person who
   built it. */
:where(a, button, .btn, .gallery-button, summary):active {
  scale: 0.985;
}

:where(a, button, .btn, .gallery-button, summary) {
  transition: scale var(--dur-instant) var(--ease-out-quad);
}

:where(a, button, input, select, textarea) {
  scroll-margin-block: var(--space-9);
}

* {
  -webkit-tap-highlight-color: rgb(0 0 0 / 0);
}

/* 16px minimum, or iOS zooms the viewport the moment a field takes focus. */
input,
select,
textarea,
button {
  font-size: max(16px, var(--step--1));
  font-family: inherit;
}

/* --------------------------------------------------------------------------
   REDUCED MOTION
   One global cut, rather than the two hand-picked exceptions this file used
   to carry.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
/* ==========================================================================
   COMPONENTS
   ========================================================================== */

/* --- Bootstrap utility overrides used by the generated markup ------------- */

.bg-offwhite {
  background-color: var(--paper) !important;
}

.text-charcoal {
  color: var(--ink);
}

/* --------------------------------------------------------------------------
   NAVBAR
   -------------------------------------------------------------------------- */

/* The `padding` here is the intended value. A `padding-top: 7rem !important`
   used to sit above it and win, putting 112px of empty space above the
   wordmark on every page - and, because this bar is sticky, dragging that
   space down the page as you scroll. */
/* Held to --nav-h so the hero's full height sum is exact, not a guess. */
.navbar {
  min-height: var(--nav-h);
  padding-block: 0;
  background-color: var(--paper);
}

.brand {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--step-1);
  letter-spacing: 0.06em;
  color: var(--ink);
}

.navbar .nav-link {
  white-space: nowrap;
  color: var(--ink);
  font-size: var(--step--1);
  line-height: var(--lh-ui);
  text-transform: uppercase;
  letter-spacing: var(--track-caps-wide);
  /* 44px minimum tap target, met with padding rather than a fixed height so
     the label can still wrap on a narrow phone. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-block: var(--space-3);
  border-bottom: 1px solid transparent;
  transition:
    border-color var(--dur-fast) var(--ease-out-quad),
    color var(--dur-fast) var(--ease-out-quad);
}

.navbar .nav-link:hover,
.navbar .nav-link:focus-visible,
.navbar .nav-link.is-active {
  color: var(--ink-muted);
}

.navbar .nav-link.is-active {
  color: var(--ink);
  border-bottom-color: var(--pink);
}

@media (max-width: 991px) {
  .navbar {
    padding-block: calc((var(--nav-h) - 44px) / 2);
  }
}

/* --------------------------------------------------------------------------
   LOADING SCREEN
   -------------------------------------------------------------------------- */

.loading-screen {
  position: fixed;
  inset: 0;
  background: var(--paper);
  display: grid;
  place-items: center;
  z-index: 2000;
  transition:
    opacity var(--dur-base) var(--ease-out-quad),
    visibility var(--dur-base) var(--ease-out-quad);
}

.loading-screen.is-hidden {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.loading-inner {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps-wide);
  color: var(--ink-muted);
}

.loading-spinner {
  width: var(--space-6);
  height: var(--space-6);
  border-radius: var(--radius-pill);
  border: 2px solid var(--line-strong);
  border-top-color: var(--ink);
  animation: spin 0.9s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* --------------------------------------------------------------------------
   HERO
   -------------------------------------------------------------------------- */

#work,
#about,
#contact,
#mailing-list {
  scroll-margin-top: var(--space-8);
}

/* Sized against the viewport, not against its contents: the navbar plus this
   plus a sliver of the first cards adds up to one screen. That sliver is the
   whole invitation to scroll - fill the screen exactly and the page looks
   like it ends here. The min and max stop it collapsing on a short laptop or
   ballooning on a tall monitor.

   `svh` is the small viewport height - the screen with the browser's chrome
   showing. It is deliberately not `dvh` here: dvh changes as the address bar
   hides on scroll, which would mean the hero resizing under the reader's
   thumb. The bare `vh` line above it is only the fallback for browsers that
   have neither. */
/* --------------------------------------------------------------------------
   THE HERO
   Asymmetric, and type led. The old hero centred one small painting in a lot
   of wall with the name underneath it, which read as an apology. This one
   sets the name at the size of a title wall and lets the painting run off the
   right edge of the screen, which is the difference between showing work and
   asking permission to.
   -------------------------------------------------------------------------- */

.hero {
  border-bottom: 0;
  display: flex;
  align-items: center;
  padding-block: var(--space-7);
  /* The whole first screen, below the navbar, at every width. svh is the
     screen with the browser's bars showing, so on a phone the hero never
     starts taller than what is visible and never resizes under a scrolling
     thumb the way dvh would. The vh line is for browsers without svh. */
  min-height: calc(100vh - var(--nav-h));
  min-height: calc(100svh - var(--nav-h));
}

/* 5fr 7fr rather than 1fr 1fr, the painting taking the larger share. Two equal columns are a split, not a
   composition - the eye has no reason to start anywhere. The asymmetry is
   what carries this, not a bleed.

   This ran to the right edge of the screen for a while, on the reasoning that
   a painting hard against the edge reads as continuing past it. It does not.
   Every painting in the catalogue is square, so nothing was ever actually
   cropped, which means nothing ran off - the picture simply stopped on the
   edge of the window, and stopping on the edge of the window is what a
   browser does to something by accident, not what a designer does on purpose.
   A bleed you cannot commit to is worse than no bleed.

   So both edges are inset now, and the max() puts them on the same line as
   the h1, the lede and the gallery at any window width. The painting hangs
   inside the room rather than being pushed through the wall. */
.hero-inner {
  width: 100%;
  display: grid;
  grid-template-columns: 5fr 7fr;
  align-items: center;
  column-gap: var(--space-7);
  padding-inline: max(
    var(--page-gutter),
    calc((100% - var(--container-wide)) / 2 + var(--page-gutter))
  );
}

.hero-name {
  font-family: var(--font-display);
  /* The brief asked for clamp(3rem, 11vw, 9rem). 9/3 is 3x, and the standard
     caps a clamp at 2.5x its min so that a reader at 200% zoom is not fighting
     a value that has already stopped growing. 7.25rem is the largest max that
     stays inside that rule, and at 1440 the name is 116px either way - the vw
     term is what is doing the work at every width below 1055px. */
  font-size: clamp(3rem, 11vw, 7.25rem);
  font-weight: 400;
  line-height: 0.86;
  letter-spacing: -0.03em;
  margin: 0;
  text-wrap: balance;
}

.hero-line {
  margin: var(--space-5) 0 0;
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  line-height: var(--lh-ui);
  color: var(--wall-muted);
  max-width: 46ch;
}

.hero-figure {
  margin: 0;
  min-width: 0;
}

/* Square and uncropped, sitting on the page's own right-hand line.
   Uncropped is the point: a screen is a letterbox and these paintings are
   not, so filling an edge would mean cutting roughly half the picture away,
   and a visitor's first sight of Virginia's work would be a fragment.

   The width is capped by the height available rather than by a max-height,
   because the element has to stay square for the shadow to sit under the
   painting rather than under a letterboxed frame. `margin-left: auto` keeps
   it on that right-hand line when the height is what is doing the capping. */
.hero-figure {
  display: flex;
  justify-content: flex-end;
}

/* The frame is exactly the painting's size, so the pink behind it can be
   placed against the painting's own edges. */
.hero-frame {
  position: relative;
  isolation: isolate;
  display: block;
  max-width: 100%;
}

.hero-img {
  display: block;
  width: auto;
  max-width: 100%;
  max-height: calc(100svh - var(--nav-h) - var(--space-7) * 2);
  height: auto;
  box-shadow: var(--shadow-hung);
}

.hero-link {
  display: inline-flex;
  align-items: center;
  /* Flex eats the whitespace text node between the label and the arrow, so
     the gap has to be put back explicitly or it reads "WORK↓". */
  gap: var(--space-2);
  min-height: 44px;
  margin-top: var(--space-4);
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--wall-muted);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  padding-bottom: var(--space-1);
  transition:
    border-color var(--dur-fast) var(--ease-out-quad),
    color var(--dur-fast) var(--ease-out-quad);
}

.hero-link:hover,
.hero-link:focus-visible {
  color: var(--ink);
  border-color: var(--ink);
}

/* The light coming up in a room. Only the type moves - the painting is the
   largest paint on the page, and animating it would delay the very measure
   the rest of this site was tuned for. */
@media (prefers-reduced-motion: no-preference) {
  .hero-name,
  .hero-line,
  .hero-link {
    animation: hero-rise var(--dur-entrance) var(--ease-out-expo) both;
  }

  .hero-line {
    animation-delay: 80ms;
  }

  .hero-link {
    animation-delay: 160ms;
  }
}

@keyframes hero-rise {
  from {
    opacity: 0;
    translate: 0 10px;
  }

  to {
    opacity: 1;
    translate: 0 0;
  }
}

/* One column below 1024, and the hero stops claiming the whole screen.
   The split stacks earlier than the rest of the site's breakpoints on
   purpose: at 768 the 5fr column is only about 300px, so the painting - the
   thing the whole page exists to show - would hang smaller in the hero than
   it does in the grid underneath. Stacked, it gets the full measure.

   The name still leads, and the painting still runs to the right edge. */
@media (max-width: 1023px) {
  .hero {
    padding-block: var(--space-6);
  }

  /* Name above, painting below, the pair centred in the screen. */
  .hero-inner {
    grid-template-columns: 1fr;
    row-gap: var(--space-7);
    align-items: start;
  }

  .hero-figure {
    justify-content: flex-start;
  }

  /* As big as the screen allows once the name above it has its room. */
  .hero-img {
    max-height: calc(100svh - var(--nav-h) - 20rem);
    max-width: min(100%, 34rem);
  }
}

/* --------------------------------------------------------------------------
   SECTION FURNITURE
   -------------------------------------------------------------------------- */

/* A peek of blank page is not a cue to scroll - it reads as the end of the
   page. Pulling the following section's top padding in means the strip under
   the hero shows the start of the work instead of nothing. */
.hero + .section-space {
  padding-top: var(--space-7);
}

.section-header {
  margin-bottom: var(--space-7);
}

.section-title {
  font-size: var(--step-3);
  line-height: var(--lh-heading);
  margin-bottom: var(--space-3);
  max-width: var(--measure-head);
}

.section-subtitle {
  color: var(--ink-muted);
  max-width: var(--measure-lead);
}

/* --- page intro (h1 + lede) ---------------------------------------------- */

.page-intro {
  max-width: var(--container-prose);
  margin-bottom: var(--space-7);
}

.page-title {
  font-size: var(--step-4);
  font-weight: 400;
  line-height: var(--lh-heading);
  letter-spacing: -0.015em;
  margin: 0 0 var(--space-4);
  max-width: var(--measure-head);
  text-wrap: balance;
}

.page-lede {
  font-size: var(--step-0);
  color: var(--wall-muted);
  line-height: var(--lh-body);
  max-width: var(--measure-lead);
  margin: 0;
}

@media (max-width: 767px) {
  .page-intro {
    margin-bottom: var(--space-6);
  }

  .section-header {
    margin-bottom: var(--space-6);
  }
}

/* --------------------------------------------------------------------------
   THE HANG
   The grid of work. There is no card here in the usual sense: no white
   panel, no 1px border, no rounded corner. The painting, its shadow and its
   label are the whole object, and the wall shows between them.
   -------------------------------------------------------------------------- */

/* Justified rows, the way a gallery hangs a line of work: every painting
   in a row is the same height, its width follows its own shape, and each row
   runs the full width of the page. Nothing is cropped and nothing floats in
   an empty slot, whatever mix of wide, tall and square work she adds.

   Each card carries its ratio (width over height) as --r. Its basis is that
   ratio times the row height, and it grows in the same proportion, so a
   row that comes up short is shared out without changing anybody's shape.
   The cap stops a lone painting at the end of a row ballooning to fill it,
   and the filler after the last card keeps the final row at the row height
   instead of stretching it across the page. */
.gallery-grid {
  --row-h: 14rem;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  column-gap: var(--space-6);
  row-gap: var(--space-8);
}

.gallery-grid::after {
  content: "";
  flex: 999 1 0;
}

@media (max-width: 1023px) {
  .gallery-grid {
    --row-h: 11rem;
    column-gap: var(--space-5);
  }
}

@media (max-width: 767px) {
  .gallery-grid {
    --row-h: 7rem;
    column-gap: var(--space-4);
    row-gap: var(--space-7);
  }
}

.gallery-card {
  flex: var(--r) 1 calc(var(--r) * var(--row-h));
  max-width: calc(var(--r) * var(--row-h) * 1.6);
  min-width: 0;
  background: transparent;
  border: 0;
  border-radius: var(--radius-base);
}

/* A piece Virginia has ticked as a feature hangs on a line of its own, at
   twice the row height, with the wall left beside it. */
.gallery-card--feature {
  flex: 0 0 100%;
  max-width: none;
  margin-block: var(--space-6);
}

.gallery-card--feature .gallery-media,
.gallery-card--feature .gallery-caption,
.gallery-card--feature .gallery-card-enquire {
  width: min(100%, calc(var(--r) * var(--row-h) * 2));
}

.gallery-media {
  position: relative;
  width: 100%;
}

/* The painting itself, whole and uncropped. Its aspect ratio comes from the
   real file, so this box is exactly the painting's shape and the shadow
   falls from its true edges. */
.gallery-frame {
  position: relative;
  display: block;
  width: 100%;
  overflow: hidden;
  background: var(--wall-deep);
  box-shadow: var(--shadow-hung);
  transition:
    transform var(--dur-base) var(--ease-out-cubic),
    box-shadow var(--dur-base) var(--ease-out-cubic);
}

/* Lifting off the wall, not floating up the page. The transform origin is
   the bottom of the frame, because that is where a picture pivots when you
   lift it - and the shadow grows to match rather than staying put. */
.gallery-card:hover .gallery-frame,
.gallery-card:focus-within .gallery-frame {
  transform: translateY(-4px) scale(1.006);
  box-shadow:
    2px 4px 4px hsl(var(--shadow-hsl) / 0.14),
    4px 8px 8px hsl(var(--shadow-hsl) / 0.14),
    8px 16px 16px hsl(var(--shadow-hsl) / 0.14),
    16px 32px 32px hsl(var(--shadow-hsl) / 0.12);
}

.gallery-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
}

/* The surface. Virginia paints in oil, and the whole
   argument for buying one is what the surface does, which a 300px tile cannot
   show. Hovering a painting cross fades to a tight crop of its most worked
   passage - see scripts/lib/details.mjs for how that passage is found.

   This is not a zoom of the picture already on screen. The file behind it is
   the 1600px derivative and the crop is 38% of it, so at a 490px card the
   crop is still drawing on more source pixels per screen pixel than the card
   itself is. It is real paint, not an upscale.

   The src is empty until the pointer arrives, so none of this costs a byte
   for a visitor who never hovers, and nothing at all on touch. */
.gallery-detail {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transform: scale(2.6);
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out-quad);
}

/* Pointers only. On touch there is no hover state to discover, and a crop
   that appears on tap would fight the tap that opens the painting. */
@media (hover: hover) {
  .gallery-card:hover .gallery-detail.is-ready,
  .gallery-card:focus-within .gallery-detail.is-ready {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .gallery-detail {
    transition: none;
  }
}

.image-loader {
  position: absolute;
  inset: 0;
  background: var(--wall-deep);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 3;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out-quad);
}

.image-loader .loading-spinner {
  width: var(--space-6);
  height: var(--space-6);
  border-color: var(--line-strong);
  border-top-color: var(--ink);
}

.is-loading .image-loader {
  opacity: 1;
}

.gallery-placeholder {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  font-size: var(--step--1);
  color: var(--wall-muted);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  background: var(--wall-deep);
}

.gallery-card.is-missing .gallery-img {
  display: none;
}

.gallery-card.is-missing .gallery-placeholder {
  display: flex;
}

.gallery-button {
  border: none;
  padding: 0;
  background: transparent;
  width: 100%;
  text-align: left;
  cursor: pointer;
  display: block;
  text-decoration: none;
  color: inherit;
}

/* The label. It sits under the painting at the painting's own width, the way
   a gallery card sits under the frame rather than under the wall - which is
   why the inset matches --hang instead of running the full column. */
.gallery-caption {
  width: 100%;
  padding-top: var(--space-4);
}

/* Three levels, and the standard asks each to differ from its neighbour on at
   least two of size, weight, colour and space. They do:

     title -> meta   case, size, colour, weight   (display ink -> small sans muted)
     meta  -> price  family, size, colour, space  (Karla small muted -> Cormorant ink,
                                                   with twice the gap above it)

   Which is what turns "Title / 1 available / £340" from a shop listing
   into a gallery record. */
/* The title as the painting is called, in the display face, rather than
   tracked capitals: it reads like a label written for the work, and a long
   one word title like Northumberland still fits a phone column. */
.gallery-card-title {
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: 400;
  letter-spacing: -0.005em;
  line-height: var(--lh-ui);
  margin: 0;
  color: var(--ink);
  hyphens: auto;
}

@media (max-width: 767px) {
  .gallery-card-title {
    font-size: var(--step-0);
  }
}

.gallery-card-meta {
  font-size: var(--step--1);
  font-weight: 300;
  letter-spacing: var(--track-body);
  line-height: var(--lh-ui);
  color: var(--wall-muted);
  margin: var(--space-2) 0 0;
  /* The medium, the size and the year are one fact about the object, so they
     break together or not at all. */
  text-wrap: pretty;
}

.gallery-card-price {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: var(--step-0);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  margin: var(--space-4) 0 0;
}

/* Sold work now hangs in the room with everything else rather than living on
   a separate page, so it needs to say so on the painting as well as under it.
   Colour is not carrying this on its own - it is a word. */
.gallery-card-flag {
  position: absolute;
  left: 0;
  bottom: 0;
  z-index: 2;
  margin: 0;
  padding: var(--space-2) var(--space-3);
  background: var(--ink);
  color: var(--paper);
  font-size: var(--step--1);
  font-weight: 500;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
}

/* Outside the card's own link, because it is a different action - one opens
   the painting, the other opens an email about it - and because a link inside
   a link is not valid HTML. It stays quiet until the card is hovered or
   anything inside it takes focus, so 16 of these do not shout over the work.
   Focus, not just hover: a keyboard user has to be able to find it. */
.gallery-card-enquire {
  width: 100%;
  margin: var(--space-3) 0 0;
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  line-height: 1;
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-out-quad);
}

.gallery-card:hover .gallery-card-enquire,
.gallery-card:focus-within .gallery-card-enquire {
  opacity: 1;
}

/* A pointer is required for the reveal above to be discoverable at all, so on
   touch it is simply always visible. */
@media (hover: none) {
  .gallery-card-enquire {
    opacity: 1;
  }
}

.gallery-card-enquire a {
  display: inline-flex;
  align-items: center;
  /* 44px of target on a 15px label, without 44px of visible box. */
  min-height: 44px;
  color: var(--wall-muted);
  text-decoration: none;
  border-bottom: 1px solid var(--line-strong);
}

.gallery-card-enquire a:hover,
.gallery-card-enquire a:focus-visible {
  color: var(--ink);
  border-color: var(--ink);
}

/* A collection with nothing in it yet. Quiet - it is a statement of fact,
   not an error - and set on the wall like the work it is waiting for. */
.collection-empty {
  margin: 0;
  padding-block: var(--space-8);
  max-width: var(--measure-lead);
  color: var(--wall-muted);
  font-size: var(--step-0);
}

.gallery-message {
  border: 1px dashed var(--line-strong);
  padding: var(--space-6);
  text-align: center;
  color: var(--ink-muted);
  background: var(--paper);
}

.gallery-message h3 {
  color: var(--ink);
  font-size: var(--step-1);
  margin-bottom: var(--space-3);
}

.gallery-message p {
  margin-bottom: var(--space-3);
}

.gallery-message ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

.gallery-message li {
  margin-bottom: var(--space-2);
}

/* --- filter by collection ------------------------------------------------
   Seaside, Landscape and any she adds. The current chip is marked by heavier
   text and a rule under it as well as by colour, and carries aria-current.
   -------------------------------------------------------------------------- */

.collection-filter {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  align-items: center;
  margin-bottom: var(--space-8);
}

.collection-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  /* 44px of target from padding rather than an empty box. */
  min-height: 44px;
  padding-block: var(--space-2);
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--wall-muted);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition:
    border-color var(--dur-fast) var(--ease-out-quad),
    color var(--dur-fast) var(--ease-out-quad);
}

.collection-chip-count {
  font-variant-numeric: tabular-nums;
  opacity: 0.65;
}

.collection-chip:hover,
.collection-chip:focus-visible {
  color: var(--ink);
  border-color: var(--ink);
}

.collection-chip.is-current {
  color: var(--ink);
  font-weight: 500;
  border-color: var(--ink);
}

/* --- the collections strip, on the collection pages ---------------------- */

.series-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-6);
  align-items: center;
  margin-bottom: var(--space-8);
}

.series-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 44px;
  padding-block: var(--space-2);
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--wall-muted);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition:
    border-color var(--dur-fast) var(--ease-out-quad),
    color var(--dur-fast) var(--ease-out-quad);
}

.series-chip:hover,
.series-chip:focus-visible {
  color: var(--ink);
  border-color: var(--ink);
}

.series-chip.is-current {
  color: var(--ink);
  font-weight: 500;
  border-color: var(--ink);
}


/* --------------------------------------------------------------------------
   THE QUOTE BAND
   The page had exactly one tonal shift in it - the contact footer - which is
   why everything above that read as a single cream field. This is the second:
   a full bleed band on a painting, two thirds of the way down, interrupting
   the hang the way a wall text interrupts a room.
   -------------------------------------------------------------------------- */

/* Out to the edges of the screen from inside .container-xl. The margin
   percentage resolves against the grid's width, so this works at any
   container width without a hardcoded gutter, and .wall clips the overflow. */
.quote-band {
  /* A line of its own in the hang, inside the page margins: a pause
     between paintings, not a band across the screen. */
  flex: 0 0 100%;
  margin-inline: 0;
  padding-inline: var(--space-8);
  margin-block: var(--space-6);
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: flex;
  align-items: center;
  min-height: 0;
  padding-block: var(--space-9);
}

/* A pause in the hang: her words, large, on a slightly deeper wall. No
   painting behind them (a painting is never cropped or darkened on this
   site) and no loud colour. The pink is only in the quote marks. */
.quote-band {
  background: var(--wall-deep);
}

.quote-band-quote .quote-mark {
  color: var(--pink);
}

/* The panel's padding does the insetting. */
.quote-band-inner {
  width: 100%;
}

/* The text sits on its own panel rather than on the painting.
   The whole band used to be darkened so a line of white type would be
   legible, which dimmed the painting everywhere to solve a problem that only
   existed where the words are. This puts the scrim exactly where the contrast
   is needed and leaves the rest of the picture alone.

   0.82 over the worst case the band is built for - a pure white pixel, in
   case a brighter painting is swapped in behind it - composites to sRGB 53,
   which is 12.5:1 for white text. The old full-band gradient's lightest stop
   managed 5.3:1.

   The measure is 34ch rather than 22. At 22 the quote ran to seven lines of
   two or three words, which reads as a column of fragments rather than a
   sentence somebody said. */
/* Ink straight on the wall, no panel. */
.quote-band-quote {
  margin: 0;
  color: var(--ink);
}

/* Measured in the quote's own type, so three lines at most at any width. */
.quote-band-quote p {
  max-width: 21ch;
}

.quote-band-quote p {
  margin: 0;
  font-family: var(--font-display);
  /* 2.75 / 1.5 is 1.83x, inside the 2.5x ceiling. */
  font-size: clamp(1.75rem, 4vw, 3.5rem);
  font-style: italic;
  font-weight: 400;
  line-height: 1.12;
  letter-spacing: -0.015em;
  text-wrap: balance;
  /* The opening quote mark hangs into the margin rather than indenting the
     first line, so the text block keeps one straight left edge. Supported in
     Safari today and harmless everywhere else - the fallback is the same
     quote mark, simply not hung. */
  hanging-punctuation: first last;
}

@media (max-width: 767px) {
  .quote-band {
    margin-block: var(--space-4);
    padding: var(--space-8) var(--space-5);
  }

  .quote-band-quote {
    max-width: none;
  }
}

/* --------------------------------------------------------------------------
   ABOUT: THE PORTRAIT SLOT AND THE SCALE SHOT
   -------------------------------------------------------------------------- */

.about-grid {
  display: grid;
  grid-template-columns: 4fr 7fr;
  gap: var(--space-7) var(--space-8);
  align-items: start;
}

@media (max-width: 900px) {
  .about-grid {
    grid-template-columns: 1fr;
  }
}

/* Virginia, beside her work. The photograph is cropped to this ratio at build
   time rather than by object-fit here, so the crop was a decision somebody
   looked at rather than whatever the middle of the frame happened to be. The
   shadow is the site's own hung shadow, lit from the same direction as every
   other shadow on the page, so she hangs on the wall with the work. */
.portrait {
  margin: var(--space-6) 0 0;
}

.portrait-img {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  background: var(--wall-deep);
  box-shadow: var(--shadow-hung);
}

/* One painting on a wall, so a price can be read against a size. Both halves
   are real photographs; the painting is placed at the same proportion the
   artwork modal's in-room preview uses for a 50cm work, so the two agree. */
.scale-shot {
  margin: var(--space-8) 0 0;
  max-width: 34rem;
}

.scale-shot-stage {
  position: relative;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  background: var(--wall-deep);
}

.scale-shot-room {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* 50% of the stage, which is what a 50cm work occupies in the modal preview,
   and lifted above centre so it hangs on the wall rather than behind the
   sideboard. The shadow is the site's own hung shadow, so the painting is lit
   from the same direction here as everywhere else. */
.scale-shot-work {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 50%;
  height: auto;
  transform: translate(-50%, -70%);
  box-shadow: var(--shadow-hung);
}

.scale-shot-caption {
  margin-top: var(--space-3);
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--wall-muted);
}

/* --------------------------------------------------------------------------
   ABOUT AND CONTACT
   -------------------------------------------------------------------------- */

.about-text {
  font-size: var(--step-0);
  color: var(--ink-muted);
  line-height: var(--lh-body);
  max-width: var(--measure-prose);
}

.about-text + .about-text {
  margin-top: var(--space-4);
}

/* The contact block sits over a painting. Two things make that safe rather
   than lucky: a scrim dark enough to hold 4.5:1 for white text against the
   brightest part of any painting Virginia might later swap in, and white kept
   to this section only. The old rule was a bare `/* Details on the left, the form on the right, and the credit under both.
   The whole band sits over a painting behind a scrim, so everything in it is
   white and the form controls have to hold their own contrast against a
   photograph rather than against a flat colour. */
.contact-grid {
  display: grid;
  grid-template-columns: 4fr 6fr;
  gap: var(--space-7) var(--space-8);
  align-items: start;
}

@media (max-width: 860px) {
  .contact-grid {
    grid-template-columns: 1fr;
  }
}

.contact-field + .contact-field {
  margin-top: var(--space-4);
}

.contact-field label {
  display: block;
  margin-bottom: var(--space-2);
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: #fff;
}

.contact-optional {
  text-transform: none;
  letter-spacing: var(--track-body);
  opacity: 0.72;
}

/* Translucent rather than solid, so the painting behind still reads through
   the band and the form does not sit on it like a pasted-on white box.

   The fill darkens rather than lightens, which is the whole trick. A white
   wash lifted the field towards the background and left white text at 4.49:1
   against the scrim's lightest stop over a white pixel - a hair under the 4.5
   it needs, and the border at 2.39:1 against the 3:1 a control needs. Going
   the other way puts the text at 8.45:1 and the border at 3.85:1 while
   letting more of the painting through, not less. Measured against the worst
   case the scrim is built for: its lightest stop over a pure white pixel, in
   case a brighter painting is ever swapped in behind it. */
.contact-form input,
.contact-form textarea {
  width: 100%;
  padding: var(--space-3);
  font-size: max(16px, var(--step-0)); /* under 16px, iOS zooms on focus */
  font-family: inherit;
  line-height: var(--lh-ui);
  color: #fff;
  background: rgb(0 0 0 / 0.28);
  border: 1px solid rgb(255 255 255 / 0.55);
  border-radius: var(--radius-base);
  transition:
    border-color var(--dur-fast) var(--ease-out-quad),
    background-color var(--dur-fast) var(--ease-out-quad);
}

.contact-form textarea {
  resize: vertical;
  min-height: 7rem;
}

.contact-form input::placeholder,
.contact-form textarea::placeholder {
  color: rgb(255 255 255 / 0.7); /* 5.13:1 on the fill above */
}

.contact-form input:hover,
.contact-form textarea:hover,
.contact-form input:focus,
.contact-form textarea:focus {
  background: rgb(0 0 0 / 0.38);
  border-color: #fff;
}

.contact-submit {
  margin-top: var(--space-5);
  min-height: 44px;
  padding: var(--space-3) var(--space-7);
  font-family: inherit;
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--ink);
  background: #fff;
  border: 1px solid #fff;
  border-radius: var(--radius-base);
  cursor: pointer;
  transition:
    background-color var(--dur-fast) var(--ease-out-quad),
    color var(--dur-fast) var(--ease-out-quad);
}

/* Shown only when a send has actually failed. Ink on paper, not a red
   banner: it is a piece of bad news that comes with a way forward, and the
   address in it is the point. */
.contact-error {
  margin: var(--space-5) 0 0;
  padding: var(--space-4);
  border: 1px solid var(--line-strong);
  border-left: 3px solid var(--ink);
  background: var(--paper-tint);
  font-size: var(--step-0);
  line-height: var(--lh-body);
  color: var(--ink);
  max-width: var(--measure-lead);
}

.contact-error a {
  color: var(--ink);
  border-bottom: 1px solid var(--line-strong);
  text-decoration: none;
}

.contact-error a:hover,
.contact-error a:focus-visible {
  border-color: var(--ink);
}

.contact-submit:hover,
.contact-submit:focus-visible {
  background: transparent;
  color: #fff;
}

/* Each of these is alone in its own paragraph, which makes it a standalone
   target rather than a link inside a sentence - so the 44px floor applies. */
.contact-text a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--line-strong);
  transition:
    border-color var(--dur-fast) var(--ease-out-quad),
    color var(--dur-fast) var(--ease-out-quad);
}

.contact-text a:hover,
.contact-text a:focus-visible {
  border-color: var(--ink);
}

/* --------------------------------------------------------------------------
   THE FOOTER
   A real one, on every page. It was one line - "Designed by Picsel" - in a
   white strip, and for a while the homepage swapped it for a contact band
   carrying a form. Neither gave somebody at the bottom of an artwork page
   anywhere to go.

   Dark, so the page has an ending. Deliberately not the painting-and-scrim
   treatment the quote band uses: that costs an image on every page, and it
   would stop the band being the one place a photograph interrupts things.
   -------------------------------------------------------------------------- */

.site-footer {
  background-color: #1d1b18;
  color: rgb(255 255 255 / 0.82);
  padding-block: var(--space-9) var(--space-6);
  border-top: 0;
  margin-top: 0;
}

.footer-grid {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr 1fr;
  gap: var(--space-7) var(--space-8);
}

@media (max-width: 1023px) {
  .footer-grid {
    grid-template-columns: 1fr 1fr;
  }
}

@media (max-width: 620px) {
  .footer-grid {
    grid-template-columns: 1fr;
    gap: var(--space-6);
  }
}

.footer-wordmark {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--step-2);
  line-height: var(--lh-heading);
  letter-spacing: -0.01em;
  color: #fff;
}

.footer-line {
  margin: var(--space-2) 0 0;
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: rgb(255 255 255 / 0.7);
  max-width: 30ch;
}

.footer-col {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.footer-heading {
  margin: 0 0 var(--space-3);
  font-family: var(--font-body);
  font-size: var(--step--1);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: #fff;
}

/* Links stack, and each is its own 44px target rather than a line of text
   with a hit area the height of the type. */
.footer-col a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-size: var(--step--1);
  color: rgb(255 255 255 / 0.82);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition:
    color var(--dur-fast) var(--ease-out-quad),
    border-color var(--dur-fast) var(--ease-out-quad);
}

.footer-col a:hover,
.footer-col a:focus-visible {
  color: #fff;
  border-color: rgb(255 255 255 / 0.6);
}

.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-3) var(--space-5);
  margin-top: var(--space-8);
  padding-top: var(--space-5);
  border-top: 1px solid rgb(255 255 255 / 0.16);
}

.footer-copyright,
.footer-credit {
  margin: 0;
  font-size: var(--step--1);
  letter-spacing: var(--track-body);
  /* 0.82 white on #1d1b18 is 9.9:1 - body text, so it needs 4.5 and has it
     with room to spare. */
  color: rgb(255 255 255 / 0.72);
}

.footer-copyright {
  max-width: 52ch;
}

.footer-credit a {
  color: #fff;
  text-decoration: none;
  border-bottom: 1px solid rgb(255 255 255 / 0.45);
}

.footer-credit a:hover,
.footer-credit a:focus-visible {
  border-color: #fff;
}

.site-footer :where(a, button):focus-visible {
  outline-color: #fff;
}

/* --------------------------------------------------------------------------
   THE CONTACT PAGE
   The form has an address of its own now. It used to be a band at the bottom
   of the homepage, which meant nothing could link to it except an anchor and
   every "Enquire" link had to send somebody to the homepage and hope.

   On paper rather than over a painting, so the fields are plain dark-on-light
   and none of the contrast arithmetic the old band needed applies.
   -------------------------------------------------------------------------- */

.contact-grid {
  display: grid;
  grid-template-columns: 4fr 6fr;
  gap: var(--space-7) var(--space-8);
  align-items: start;
}

@media (max-width: 860px) {
  .contact-grid {
    grid-template-columns: 1fr;
  }
}

.contact-heading {
  font-family: var(--font-body);
  font-size: var(--step--1);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--ink);
  margin: 0 0 var(--space-3);
}

.contact-heading + .contact-text {
  margin-top: 0;
}

.contact-text + .contact-heading {
  margin-top: var(--space-7);
}

.contact-text {
  font-size: var(--step-0);
  line-height: var(--lh-body);
  color: var(--ink-muted);
  max-width: var(--measure-lead);
  margin: 0 0 var(--space-3);
}

.contact-field + .contact-field {
  margin-top: var(--space-5);
}

.contact-field label {
  display: block;
  margin-bottom: var(--space-2);
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--ink);
}

.contact-optional {
  text-transform: none;
  letter-spacing: var(--track-body);
  color: var(--ink-muted);
}

.contact-form input,
.contact-form textarea,
.contact-form select {
  width: 100%;
  padding: var(--space-3);
  font-size: max(16px, var(--step-0)); /* under 16px, iOS zooms on focus */
  font-family: inherit;
  line-height: var(--lh-ui);
  color: var(--ink);
  background: var(--paper-tint);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-base);
  transition:
    border-color var(--dur-fast) var(--ease-out-quad),
    background-color var(--dur-fast) var(--ease-out-quad);
}

.contact-form textarea {
  resize: vertical;
  min-height: 9rem;
}

.contact-form input::placeholder,
.contact-form textarea::placeholder {
  color: var(--ink-muted);
}

.contact-form input:hover,
.contact-form textarea:hover,
.contact-form select:hover,
.contact-form input:focus,
.contact-form textarea:focus,
.contact-form select:focus {
  background-color: #fff;
  border-color: var(--ink);
}

.contact-submit {
  margin-top: var(--space-6);
  min-height: 44px;
  padding: var(--space-3) var(--space-7);
  font-family: inherit;
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--paper);
  background: var(--ink);
  border: 1px solid var(--ink);
  border-radius: var(--radius-base);
  cursor: pointer;
  transition:
    background-color var(--dur-fast) var(--ease-out-quad),
    color var(--dur-fast) var(--ease-out-quad);
}

/* Shown only when a send has actually failed. Ink on paper, not a red
   banner: it is a piece of bad news that comes with a way forward, and the
   address in it is the point. */
.contact-error {
  margin: var(--space-5) 0 0;
  padding: var(--space-4);
  border: 1px solid var(--line-strong);
  border-left: 3px solid var(--ink);
  background: var(--paper-tint);
  font-size: var(--step-0);
  line-height: var(--lh-body);
  color: var(--ink);
  max-width: var(--measure-lead);
}

.contact-error a {
  color: var(--ink);
  border-bottom: 1px solid var(--line-strong);
  text-decoration: none;
}

.contact-error a:hover,
.contact-error a:focus-visible {
  border-color: var(--ink);
}

.contact-submit:hover,
.contact-submit:focus-visible {
  background: transparent;
  color: var(--ink);
}

/* ==========================================================================
   ARTWORK PAGE
   ========================================================================== */

.artwork-breadcrumb {
  font-size: var(--step--1);
  color: var(--ink-muted);
  margin-bottom: var(--space-6);
  letter-spacing: var(--track-body);
}

.artwork-breadcrumb a {
  color: var(--ink-muted);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur-fast) var(--ease-out-quad);
}

.artwork-breadcrumb a:hover,
.artwork-breadcrumb a:focus-visible {
  border-color: var(--ink-muted);
}

/* The single painting, hung, with wall behind it rather than page. This is
   the same object as a card in the grid, at the size it deserves. */
.artwork-stage {
  padding: var(--space-6);
  display: grid;
  place-items: center;
}

.artwork-image {
  width: 100%;
  height: auto;
  display: block;
  box-shadow: var(--shadow-hung);
}

.artwork-title {
  font-size: var(--step-3);
  font-weight: 400;
  line-height: var(--lh-heading);
  letter-spacing: -0.015em;
  margin: 0 0 var(--space-4);
  max-width: var(--measure-head);
}

.artwork-summary {
  font-weight: 500;
  margin-bottom: var(--space-4);
  max-width: var(--measure-prose);
}

.artwork-description {
  color: var(--ink-muted);
  line-height: var(--lh-body);
  margin-bottom: var(--space-6);
  max-width: var(--measure-prose);
}

/* A specification table, so it is set like one: tabular figures, the label
   in the caps voice, the value hard right against it. */
.artwork-details {
  margin: 0 0 var(--space-6);
  border-top: 1px solid var(--line-strong);
}

.artwork-detail {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-5);
  padding-block: var(--space-3);
  border-bottom: 1px solid var(--line);
}

.artwork-detail dt {
  font-weight: 500;
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--ink-muted);
}

.artwork-detail dd {
  margin: 0;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.artwork-price {
  font-family: var(--font-display);
  font-size: var(--step-2);
  font-variant-numeric: tabular-nums;
  margin-bottom: var(--space-4);
}

.artwork-availability,
.artwork-status {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--ink-muted);
  margin-bottom: var(--space-2);
}

/* The one primary action on the page. Solid, full width, and the only thing
   on the page carrying the ink fill. */
.artwork-enquire-solid {
  display: block;
  width: 100%;
  min-height: 44px;
  text-align: center;
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  font-size: max(16px, var(--step--1));
  line-height: var(--lh-ui);
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-base);
  background-color: var(--ink);
  color: var(--paper);
  border: 1px solid var(--ink);
  transition:
    background-color var(--dur-fast) var(--ease-out-quad),
    color var(--dur-fast) var(--ease-out-quad);
}

@media (hover: hover) {
  .artwork-enquire-solid:hover {
    background-color: var(--wall-muted);
    border-color: var(--wall-muted);
    color: var(--paper);
  }
}

.artwork-enquire-solid:focus-visible {
  color: var(--paper);
}

.artwork-enquire {
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  font-size: max(16px, var(--step--1));
  min-height: 44px;
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-base);
}

.artwork-share {
  display: inline-block;
  margin-top: var(--space-5);
  font-size: var(--step--1);
  color: var(--ink-muted);
}

.artwork-pager {
  display: flex;
  justify-content: space-between;
  gap: var(--space-6);
  margin-top: var(--space-9);
  padding-top: var(--space-6);
  border-top: 1px solid var(--line-strong);
}

.artwork-pager-link {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  text-decoration: none;
  color: var(--ink);
  padding-block: var(--space-2);
}

.artwork-pager-next {
  margin-left: auto;
  text-align: right;
}

/* Was 0.72rem, or 11.5px. Nothing on this site is asked to be read below
   --step--1 now. */
.artwork-pager-label {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--ink-muted);
}

.artwork-pager-title {
  font-family: var(--font-display);
  font-size: var(--step-0);
  transition: color var(--dur-fast) var(--ease-out-quad);
}

.artwork-pager-link:hover .artwork-pager-title,
.artwork-pager-link:focus-visible .artwork-pager-title {
  color: var(--ink-muted);
}

/* --- inline room preview -------------------------------------------------- */

.artwork-preview {
  padding: var(--space-4) 0 var(--space-8);
}

.artwork-preview-title {
  font-size: var(--step-2);
  margin-bottom: var(--space-5);
}

.artwork-preview-frame {
  position: relative;
  width: 100%;
  max-width: 620px;
  aspect-ratio: 4 / 3;
  background: var(--wall-deep);
  overflow: hidden;
}

.artwork-preview-frame .preview-stage {
  width: 100%;
  height: 100%;
}

.artwork-preview-controls {
  max-width: 620px;
  margin-top: var(--space-5);
  margin-bottom: 0;
}

/* ==========================================================================
   EXHIBITIONS
   ========================================================================== */

.exhibition-heading {
  font-family: var(--font-body);
  font-size: var(--step--1);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--ink-muted);
  margin: var(--space-8) 0 var(--space-5);
  max-width: none;
}

.exhibition-heading:first-of-type {
  margin-top: 0;
}

.exhibition-list {
  margin: 0 0 var(--space-4);
  padding: 0;
  display: grid;
  gap: var(--space-6);
}

.exhibition-entry {
  padding-bottom: var(--space-6);
  border-bottom: 1px solid var(--line);
}

.exhibition-entry:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.exhibition-dates {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--ink-muted);
  margin-bottom: var(--space-2);
  font-variant-numeric: tabular-nums;
}

.exhibition-title {
  font-size: var(--step-1);
  font-weight: 400;
  margin: 0 0 var(--space-2);
}

.exhibition-title a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid var(--line-strong);
  transition:
    border-color var(--dur-fast) var(--ease-out-quad),
    color var(--dur-fast) var(--ease-out-quad);
}

.exhibition-press a {
  color: var(--ink);
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  text-decoration: none;
  border-bottom: 1px solid var(--line-strong);
  transition: border-color var(--dur-fast) var(--ease-out-quad);
}

.exhibition-title a:hover,
.exhibition-title a:focus-visible,
.exhibition-press a:hover,
.exhibition-press a:focus-visible {
  border-color: var(--pink-ink);
}

.exhibition-venue {
  margin: 0 0 var(--space-2);
  color: var(--ink);
  max-width: var(--measure-prose);
}

.exhibition-note {
  margin: 0;
  color: var(--ink-muted);
  max-width: var(--measure-prose);
}

.exhibition-press {
  margin: var(--space-2) 0 0;
}

.exhibition-empty {
  color: var(--ink-muted);
  margin-bottom: var(--space-4);
}

/* ==========================================================================
   404
   ========================================================================== */

.notfound-links {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-5);
  align-items: center;
  font-size: var(--step--1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--ink-muted);
}

.notfound-links a {
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--line-strong);
  padding-block: var(--space-2);
  transition:
    border-color var(--dur-fast) var(--ease-out-quad),
    color var(--dur-fast) var(--ease-out-quad);
}

.notfound-links a:hover,
.notfound-links a:focus-visible {
  color: var(--ink-muted);
  border-color: var(--ink-muted);
}

/* ==========================================================================
   ACCORDION
   ========================================================================== */

.gallery-accordion .accordion-button {
  background: var(--paper);
  color: var(--ink);
  font-weight: 500;
  font-size: max(16px, var(--step--1));
  letter-spacing: var(--track-body);
  min-height: 44px;
  border-radius: var(--radius-base);
}

.gallery-accordion .accordion-item {
  border: 1px solid var(--line);
  border-radius: var(--radius-base);
  overflow: hidden;
}

.gallery-accordion .accordion-body {
  background: var(--paper);
}

.gallery-steps {
  margin: 0;
  padding-left: var(--space-5);
  color: var(--ink-muted);
  line-height: var(--lh-body);
}
/* ==========================================================================
   MODAL AND ROOM PREVIEW
   ========================================================================== */

.modal-content {
  border-radius: var(--radius-base);
  overflow: hidden;
  border: none;
  max-height: 92svh;
}

.modal-body {
  position: relative;
  max-height: 92svh;
  overflow: hidden;
}

.modal-close {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  z-index: 2;
  width: 44px;
  height: 44px;
  padding: 0;
  background-color: rgb(255 255 255 / 0.92);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-md);
}

.modal-backdrop.show {
  opacity: 0.85;
}

.modal-figure {
  margin: 0;
  background: var(--paper);
  display: flex;
  align-items: stretch;
}

/* The painting side of the modal is wall, not white - same object, same
   ground, wherever you meet it. */
.modal-media {
  width: 60%;
  background: var(--wall);
  position: relative;
}

.modal-img {
  width: 100%;
  height: auto;
  display: block;
}

.modal-caption {
  width: 40%;
  padding: var(--space-6);
  color: var(--ink-muted);
  font-size: var(--step--1);
  border-left: 1px solid var(--line);
  display: flex;
  flex-direction: column;
}

.modal-title {
  color: var(--ink);
  font-size: var(--step-2);
  margin-bottom: var(--space-2);
}

.modal-meta {
  margin: var(--space-4) 0 var(--space-6);
  display: grid;
  gap: var(--space-1);
  font-variant-numeric: tabular-nums;
}

.modal-description {
  margin: 0;
  max-width: var(--measure-prose);
}

.modal-preview-btn {
  margin-top: auto;
  align-self: flex-end;
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  font-size: max(16px, var(--step--1));
  min-height: 44px;
  border-radius: var(--radius-base);
}

.modal-slider-body {
  background: var(--paper);
}

/* Was 450ms on the browser default `ease`. The standard's ceiling for an
   interface movement is 300ms, and the curve has to decelerate or the panel
   arrives feeling heavier than it is. */
.modal-slider {
  display: flex;
  width: 200%;
  transform: translateX(0%);
  transition: transform var(--dur-base) var(--ease-out-cubic);
}

.modal-slider.is-preview {
  transform: translateX(-50%);
}

.modal-panel {
  width: 50%;
  flex: 0 0 50%;
}

.modal-panel .modal-figure {
  height: 100%;
}

.modal-panel-preview .modal-media {
  height: 100%;
}

.preview-header {
  margin-bottom: var(--space-5);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
}

.preview-controls {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.preview-label {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--ink-muted);
}

.preview-select {
  min-width: 12rem;
  min-height: 44px;
  font-size: max(16px, var(--step--1));
  border-radius: var(--radius-base);
}

.preview-stage {
  position: relative;
  width: 100%;
  height: 100%;
  aspect-ratio: auto;
  background: transparent;
  display: grid;
  place-items: center;
  overflow: hidden;
}

.preview-stage::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--ink);
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-out-quad);
  z-index: 4;
  pointer-events: none;
}

.preview-scene {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity var(--dur-base) var(--ease-out-quad);
  display: block;
}

.preview-scene.is-fading {
  opacity: 0;
}

.preview-artwork,
.preview-light,
.preview-shadow {
  transition: opacity var(--dur-base) var(--ease-out-quad);
}

.preview-stage.is-fading .preview-scene,
.preview-stage.is-fading .preview-artwork,
.preview-stage.is-fading .preview-light,
.preview-stage.is-fading .preview-shadow {
  opacity: 0;
}

/* The room preview keeps its own shadow rather than --shadow-hung: this one
   is cast by the light in the photographed room, not by the site's light, so
   it is positioned by the scene and not by the token. */
.preview-artwork {
  position: absolute;
  top: 50%;
  left: 50%;
  max-width: 60%;
  max-height: 60%;
  object-fit: contain;
  z-index: 2;
  box-shadow: 5px 5px 7px rgb(0 0 0 / 0.3);
  display: block;
}

.preview-light {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 3;
  pointer-events: none;
  background: linear-gradient(90deg, rgb(255 255 255 / 0.1), rgb(0 0 0 / 0.5));
  mix-blend-mode: soft-light;
}

.preview-shadow {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 35%;
  height: 8%;
  background: rgb(0 0 0 / 0.2);
  filter: blur(18px);
  opacity: 0.25;
  z-index: 1;
  border-radius: 0;
  transform: translateY(35%);
}

@media (max-width: 991px) {
  .modal-slider {
    flex-direction: column;
    width: 100%;
    transform: none;
  }

  .modal-slider.is-preview {
    transform: none;
  }

  .modal-panel {
    width: 100%;
    flex: 0 0 100%;
  }

  .modal-panel-details {
    display: block;
  }

  .modal-panel-preview {
    display: none;
  }

  .modal-slider.is-preview .modal-panel-details {
    display: none;
  }

  .modal-slider.is-preview .modal-panel-preview {
    display: block;
  }

  /* `dvh` is right here and wrong for the hero: a dialog is opened, read and
     dismissed without scrolling, so it wants the viewport as it currently
     is. The bare `vh` above it is the fallback. */
  .modal-dialog {
    margin: var(--space-4);
    max-width: calc(100% - var(--space-6));
    height: calc(100vh - var(--space-6));
    height: calc(100dvh - var(--space-6));
  }

  .modal-content {
    height: 100%;
    max-height: 100%;
  }

  .modal-body {
    height: 100%;
    max-height: 100%;
    overflow: auto;
  }

  .modal-figure {
    flex-direction: column;
    height: 100%;
  }

  .modal-media,
  .modal-caption {
    width: 100%;
  }

  .modal-media {
    height: 50svh;
    min-height: 45svh;
  }

  .modal-panel-preview .modal-media {
    height: 55svh;
  }

  .modal-caption {
    border-left: none;
    border-top: 1px solid var(--line);
    padding: var(--space-4);
  }

  .modal-img {
    height: 100%;
    object-fit: cover;
  }

  .preview-scene {
    object-fit: cover;
  }

  .preview-stage {
    min-height: 45svh;
  }
}

/* ==========================================================================
   JOURNAL

   Reading, so: paper, a prose measure, and the same type scale as every
   other page of words. The only wall on these pages is the small frame
   behind a related painting's thumbnail - a painting is a painting
   wherever you meet it.
   ========================================================================== */

.journal-heading {
  font-size: var(--step-4);
  font-weight: 400;
  margin: 0 0 var(--space-4);
  max-width: var(--measure-head);
}

.journal-intro {
  font-size: var(--step-0);
  color: var(--ink-muted);
  max-width: var(--measure-lead);
  margin: 0 0 var(--space-8);
}

.journal-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-7);
}

.journal-entry {
  padding-bottom: var(--space-7);
  border-bottom: 1px solid var(--line);
}

.journal-entry:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.journal-entry-link {
  display: grid;
  grid-template-columns: 140px 1fr;
  gap: var(--space-5);
  align-items: start;
  color: inherit;
  text-decoration: none;
}

/* An entry with no cover should not leave a 140px hole where one would be. */
.journal-entry-link:not(:has(.journal-entry-cover)) {
  grid-template-columns: 1fr;
}

.journal-entry-cover img {
  display: block;
  width: 100%;
  height: auto;
  background: var(--paper-tint);
}

.journal-entry-title {
  display: block;
  font-family: var(--font-display);
  font-size: var(--step-1);
  margin-bottom: var(--space-2);
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur-fast) var(--ease-out-quad);
}

.journal-entry-link:hover .journal-entry-title,
.journal-entry-link:focus-visible .journal-entry-title {
  border-bottom-color: var(--line-strong);
}

.journal-entry-date {
  display: block;
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
  margin-bottom: var(--space-3);
}

.journal-entry-excerpt {
  display: block;
  color: var(--ink-muted);
  max-width: var(--measure-prose);
}

.journal-empty {
  color: var(--ink-muted);
  max-width: var(--measure-lead);
}

/* ---- One post ---- */

.post-breadcrumb {
  font-size: var(--step--1);
  color: var(--ink-muted);
  margin-bottom: var(--space-6);
}

.post-breadcrumb a {
  color: inherit;
}

.post-header {
  margin-bottom: var(--space-7);
}

.post-title {
  font-size: var(--step-4);
  font-weight: 400;
  margin: 0 0 var(--space-3);
  max-width: var(--measure-head);
}

.post-date {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
  margin: 0;
}

/* The measure is the whole point of this block: prose set to the width of a
   page rather than the width of a monitor. */
.post-body {
  max-width: var(--measure-prose);
}

.post-body p {
  font-size: var(--step-0);
  margin: 0 0 var(--space-5);
}

.post-body h2 {
  font-size: var(--step-2);
  font-weight: 400;
  margin: var(--space-8) 0 var(--space-4);
}

.post-body a {
  color: inherit;
  border-bottom: 1px solid var(--line-strong);
  text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease-out-quad);
}

.post-body a:hover,
.post-body a:focus-visible {
  border-bottom-color: var(--ink);
}

.post-photo {
  margin: var(--space-8) 0;
}

.post-photo img {
  display: block;
  width: 100%;
  height: auto;
}

/* Under the photo, in the same quiet caption voice as the painting records. */
.post-photo-caption {
  margin-top: var(--space-2);
  font-size: var(--step--1);
  line-height: var(--lh-body);
  color: var(--ink-muted);
  max-width: var(--measure-prose);
}

.post-related {
  margin-top: var(--space-9);
  padding-top: var(--space-7);
  border-top: 1px solid var(--line);
}

.post-related-heading {
  font-family: var(--font-body);
  font-size: var(--step--1);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--ink-muted);
  margin: 0 0 var(--space-5);
}

.post-related-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: var(--space-5);
}

.post-related-card {
  color: inherit;
  text-decoration: none;
  display: block;
}

/* The one place a journal page carries the wall: these are paintings, and a
   painting hangs on a wall wherever it appears. */
.post-related-frame {
  display: block;
  padding: var(--space-4);
  margin-bottom: var(--space-3);
}

.post-related-frame img {
  display: block;
  width: 100%;
  height: auto;
  box-shadow: var(--shadow-hang, 0 8px 24px rgba(31, 31, 31, 0.14));
}

.post-related-title {
  display: block;
  font-size: var(--step--1);
  color: var(--ink-muted);
}

.post-related-card:hover .post-related-title,
.post-related-card:focus-visible .post-related-title {
  color: var(--ink);
}

/* The line on an artwork page pointing at the post that discusses it. */
.artwork-journal {
  font-size: var(--step--1);
  color: var(--ink-muted);
  margin-top: var(--space-5);
}

.artwork-journal a {
  color: inherit;
  border-bottom: 1px solid var(--line-strong);
  text-decoration: none;
}

@media (max-width: 575.98px) {
  .journal-entry-link {
    grid-template-columns: 1fr;
  }

  .journal-entry-cover img {
    max-width: 220px;
  }
}

/* --------------------------------------------------------------------------
   MORE PHOTOS OF A PAINTING
   A detail or a room shot under the main photo, two across. Plain frames:
   these are photographs of the work, so nothing sits on top of them.
   -------------------------------------------------------------------------- */

.artwork-more {
  list-style: none;
  margin: var(--space-5) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4);
}

.artwork-more a {
  display: block;
}

.artwork-more img {
  display: block;
  width: 100%;
  height: auto;
}

.artwork-more a:focus-visible {
  outline: 2px solid var(--pink-ink);
  outline-offset: 3px;
}

/* --------------------------------------------------------------------------
   READING PAGES: the About page and the journal
   Bootstrap's container-md carries a 12px gutter; every other page uses the
   house gutter, so the text edge jumped between pages on a phone.
   -------------------------------------------------------------------------- */

.container-md {
  padding-left: var(--page-gutter);
  padding-right: var(--page-gutter);
}

.about-page-text {
  max-width: var(--measure-prose);
}

.about-press {
  margin-top: var(--space-8);
}

/* The house prose link, as in a journal post. */
.about-text a,
.about-press a {
  color: inherit;
  border-bottom: 1px solid var(--line-strong);
  text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease-out-quad);
}

.about-text a:hover,
.about-text a:focus-visible,
.about-press a:hover,
.about-press a:focus-visible {
  border-bottom-color: var(--ink);
}

/* The contact form's one dropdown, drawn to match the text fields rather
   than left as the browser's grey control. */
.contact-form select {
  appearance: none;
  padding-right: calc(var(--space-3) * 3);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%2326241f' stroke-width='1.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
  background-size: 12px 8px;
  cursor: pointer;
}

/* --------------------------------------------------------------------------
   ABOUT: PHOTOS OF VIRGINIA AND OUT PAINTING
   Photos of her and of her out working, never paintings, so they get no
   wall and no hung shadow: they are snapshots on paper, captioned with the
   place, which is the same documentary voice as the painting captions.
   -------------------------------------------------------------------------- */

.about-photo {
  margin: 0;
}

.about-photo img {
  display: block;
  width: 100%;
  height: auto;
  background: var(--wall-deep);
}

/* Portraits are shown 4:5 whatever shape the phone took them in, cropped
   from the middle. Only ever photos of her, never a painting. */
.about-photo--portrait img {
  aspect-ratio: 4 / 5;
  object-fit: cover;
}

.about-photo-caption {
  margin-top: var(--space-2);
  font-size: var(--step--1);
  line-height: var(--lh-ui);
  color: var(--ink-muted);
}

.about-text--lead {
  font-size: var(--step-1);
  color: var(--ink);
  line-height: 1.45;
}

.about-signoff {
  margin-top: var(--space-5);
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--step-1);
  color: var(--ink);
}

.about-subhead {
  margin-top: var(--space-8);
}

/* The places, as a run of names rather than a list: it reads like the
   caption under a painting, and on a phone it simply wraps. */
.about-places {
  margin-top: var(--space-5);
  max-width: var(--measure-prose);
  font-family: var(--font-display);
  font-size: var(--step-1);
  line-height: 1.5;
  color: var(--ink);
}

.about-places span {
  color: var(--pink-ink);
}

/* The text on the left and the photo on the right, so the page doesn't
   stack every section the same way round. */
.about-grid--flip {
  grid-template-columns: 7fr 4fr;
}

@media (max-width: 900px) {
  .about-grid--flip {
    grid-template-columns: 1fr;
  }

  .about-grid--flip .about-side {
    order: -1;
  }
}

.about-side--pair {
  display: grid;
  gap: var(--space-6);
}

/* Four across on a desktop, two on a phone. Portrait slots, because nearly
   every plein air photo was taken upright on a phone. */
.about-plein-air {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-6) var(--space-5);
  margin-top: var(--space-6);
}

.about-plein-air img {
  aspect-ratio: 3 / 4;
  object-fit: cover;
}

@media (max-width: 767px) {
  .about-plein-air {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-5) var(--space-4);
  }
}

.about-text a:hover,
.about-text a:focus-visible,
.about-press a:hover,
.about-press a:focus-visible {
  border-bottom-color: var(--pink-ink);
}

/* --------------------------------------------------------------------------
   PRINTS: THE PRICE LIST
   -------------------------------------------------------------------------- */

.print-prices {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4) var(--space-6);
  margin: var(--space-6) 0 0;
  padding: var(--space-5) 0;
  border-block: 1px solid var(--line-strong);
}

.print-prices li {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
}

.print-size {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--wall-muted);
}

.print-price {
  font-family: var(--font-display);
  font-size: var(--step-2);
  color: var(--ink);
}

.page-note {
  margin: var(--space-4) 0 0;
  font-size: var(--step--1);
  color: var(--wall-muted);
  max-width: var(--measure-lead);
}

/* Under the price: the print, for anyone the original is too much for or
   who missed it. Quiet, so it never competes with the enquiry button. */
.artwork-prints {
  margin: var(--space-5) 0 0;
  font-size: var(--step--1);
  color: var(--ink-muted);
}

.artwork-prints a {
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--line-strong);
  transition: border-color var(--dur-fast) var(--ease-out-quad);
}

.artwork-prints a:hover,
.artwork-prints a:focus-visible {
  border-color: var(--pink-ink);
}

/* --------------------------------------------------------------------------
   PINK, IN SMALL AMOUNTS
   Her shocking pink, used the way it shows through her paintings: a wee bit
   here and there, mostly when something is pointed at. Gathered in one place
   so it is easy to turn up, down or off. --pink-ink on light grounds keeps
   text contrast above 4.5:1; the brighter --pink is only for thin lines.
   -------------------------------------------------------------------------- */

/* The painting's title warms to pink as the painting lifts off the wall. */
.gallery-card-title {
  transition: color var(--dur-fast) var(--ease-out-quad);
}

.gallery-card:hover .gallery-card-title,
.gallery-card:focus-within .gallery-card-title {
  color: var(--pink-ink);
}

.navbar .nav-link:hover,
.navbar .nav-link:focus-visible {
  color: var(--ink);
  border-bottom-color: var(--pink);
}

.hero-link:hover,
.hero-link:focus-visible,
.collection-chip:hover,
.collection-chip:focus-visible,
.collection-chip.is-current,
.series-chip:hover,
.series-chip.is-current,
.link-quiet:hover,
.link-quiet:focus-visible,
.gallery-card-enquire a:hover,
.gallery-card-enquire a:focus-visible,
.contact-text a:hover,
.contact-text a:focus-visible,
.artwork-breadcrumb a:hover,
.notfound-links a:hover,
.notfound-links a:focus-visible {
  border-color: var(--pink-ink);
}

.artwork-pager-link:hover .artwork-pager-title,
.artwork-pager-link:focus-visible .artwork-pager-title {
  color: var(--pink-ink);
}

@media (hover: hover) {
  .artwork-enquire-solid:hover {
    background-color: var(--pink-ink);
    border-color: var(--pink-ink);
    color: #fff;
  }
}

.contact-form .contact-submit:hover,
.contact-form .contact-submit:focus-visible {
  background: var(--pink-ink);
  border-color: var(--pink-ink);
  color: #fff;
}

.footer-col a:hover,
.footer-col a:focus-visible,
.footer-credit a:hover {
  border-color: var(--pink);
}

/* At rest, not only on hover. Ben asked for more of it on 5 October 2026,
   still as lines and labels rather than fills. */

.hero-dot {
  color: var(--pink-ink);
}

.hero-link {
  color: var(--pink-ink);
  border-bottom-color: var(--pink);
}

.hero-link:hover,
.hero-link:focus-visible {
  color: var(--ink);
}

.artwork-pager-label,
.exhibition-dates,
.artwork-detail dt,
.journal-entry-date,
.post-date {
  color: var(--pink-ink);
}

.footer-heading {
  color: var(--pink-on-dark);
}

/* --------------------------------------------------------------------------
   THE HERO ON A PHONE
   Stacked, the name was a polite line of 58px with a screen of empty wall
   above it. Here it fills the width, two words on two lines, set heavier
   because Cormorant's hairlines go faint at phone brightness, and the
   hero sits at the top rather than floating in the middle of the screen.
   -------------------------------------------------------------------------- */
@media (max-width: 1023px) {
  .hero {
    min-height: 0;
    padding-block: var(--space-6) var(--space-5);
  }

  .hero-inner {
    row-gap: var(--space-6);
  }

  .hero-name {
    font-size: clamp(3.5rem, 21vw, 7.25rem);
    font-weight: 600;
    line-height: 0.82;
    letter-spacing: -0.02em;
  }

  .hero-line {
    color: var(--ink);
    font-weight: 500;
  }

  .hero-link {
    font-weight: 600;
  }

  .hero-img {
    max-height: none;
  }
}
