/* ==========================================================================
   Tully Grove — landing page

   Everything here is taken from the paintings themselves.

   The colours are measured, not chosen. --paper is the tone of her unpainted
   sheet, sampled from the margins of the five watercolours that have one:
   they agree to within a couple of levels. --ink is the weighted mean of the
   darkest passages across the collection, which is why it is a blue slate
   rather than black — her deepest values are the diver's wetsuit, the
   penguin's back and the navy headland, not soot. The accents are the two
   families that recur most: the terracotta of the saloon and the sand, and
   the green-teal of the water.

   The name is set in her own hand: logo.svg is the painted tullygrove sign,
   photographed, lifted off its wall and traced to vector. Newsreader does the
   reading around it. No sans anywhere, and no display face either now that the
   lettering carries the masthead itself.
   ========================================================================== */

@font-face {
  font-family: 'Newsreader';
  src: url('fonts/newsreader.woff2') format('woff2');
  font-weight: 200 700;
  font-style: normal;
  font-display: swap;
  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: 'Newsreader';
  src: url('fonts/newsreader-italic.woff2') format('woff2');
  font-weight: 200 700;
  font-style: italic;
  font-display: swap;
  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;
}

:root {
  --paper: #ece7e0;        /* her sheet, measured from the unpainted margins */
  --paper-lit: #f5f1ea;
  --paper-deep: #ded5c7;
  --ink: #2f3244;          /* the mean of her darkest passages */
  --ink-soft: #565968;
  --ink-faint: #626573;
  --terracotta: #8f4034;   /* the saloon, the sand, the buccaroos */
  --teal: #33707a;         /* the water, the sea below the cliffs */
  --rule: #d3cbbd;

  --text: 'Newsreader', 'Iowan Old Style', Georgia, 'Times New Roman', serif;

  --gutter: clamp(1.25rem, 4vw, 4.5rem);
  --plate-gap: clamp(0.6rem, 1.2vw, 1.1rem);
}

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  /* Every svh below is preceded by a plain fallback. iOS Safari before 15.4
     does not know the unit, and an unsupported unit does not degrade — it
     invalidates the whole declaration. A collage left with no height has
     plates with no height, which is precisely "it isn't loading". */
  min-height: 100vh;
  min-height: 100svh;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--text);
  font-weight: 400;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

/* A wash so the page isn't a flat field, and a fine grain standing in for the
   tooth of the paper. Both sit behind the content. */
body::before,
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

body::before {
  background:
    radial-gradient(70rem 50rem at 10% 4%, var(--paper-lit) 0%, transparent 62%),
    radial-gradient(60rem 42rem at 94% 98%, var(--paper-deep) 0%, transparent 66%);
}

body::after {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)' opacity='0.4'/%3E%3C/svg%3E");
  opacity: 0.28;
  mix-blend-mode: multiply;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 10;
  padding: 0.75rem 1.1rem;
  background: var(--ink);
  color: var(--paper);
  font-size: 0.9rem;
  border-radius: 0 0 4px 0;
}
.skip-link:focus { left: 0; }

/* --------------------------------------------------------------- the hero */

.hero {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100svh;
  padding:
    max(clamp(1.5rem, 4vh, 3rem), env(safe-area-inset-top))
    max(var(--gutter), env(safe-area-inset-right))
    0
    max(var(--gutter), env(safe-area-inset-left));
}

.hero__grid {
  position: relative;      /* the lever hangs off its right edge */
  flex: 1;
  display: grid;
  grid-template-columns: minmax(0, 25rem) minmax(0, 1fr);
  grid-template-areas:
    "intro art"
    "contact art";
  align-content: center;
  column-gap: clamp(2rem, 4.5vw, 5rem);
  row-gap: clamp(1.5rem, 3.5vh, 2.5rem);
  width: 100%;
  max-width: 84rem;
  margin-inline: auto;
}

/* The collage spans both rows and is taller than either, so the spare height
   gets shared between them. Pinning the intro to the bottom of its row and the
   form to the top of the next keeps the two together in the middle of the
   column instead of drifting apart. */
.hero__intro   { grid-area: intro; align-self: end; }
.hero__contact { grid-area: contact; align-self: start; }
.collage       { grid-area: art; align-self: center; }

/* Her own lettering, traced to vector from a photograph of the painted sign.
   See tools/build_logo.py. */
.wordmark {
  margin: 0;
  line-height: 0;
}

.wordmark img {
  display: block;
  width: 100%;
  max-width: 30rem;
  height: auto;
  /* Drawn left to right over a beat and a half, the way it was written. The
     flourish enters from the left, so the wipe runs with the pen rather than
     against it. */
  animation: write-on 1.5s cubic-bezier(0.62, 0.03, 0.32, 1) both;
}

@keyframes write-on {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}

/* The y and g descenders drop to 96% of the logo's box, but only across the
   middle twelfth of its width; everywhere else the ink stops at 58%. Tucking
   this line up into that space therefore runs it straight through the loops,
   so it sits clear beneath them instead and the loops fill the gap. */
.medium {
  margin: 0.2rem 0 0;
  font-size: clamp(1rem, 0.5vw + 0.88rem, 1.15rem);
  font-style: italic;
  font-weight: 350;
  letter-spacing: 0.01em;
  color: var(--terracotta);
}

.lede {
  margin: clamp(1.1rem, 2.6vh, 1.7rem) 0 0;
  max-width: 30rem;
  font-size: clamp(1.0625rem, 0.7vw + 0.88rem, 1.3125rem);
  font-weight: 380;
  line-height: 1.58;
  color: var(--ink-soft);
  text-wrap: pretty;
}

/* What is actually for sale, stated plainly and in full ink so it reads as an
   offer rather than as more description. */
.services {
  margin: 0.85rem 0 0;
  font-size: clamp(1rem, 0.4vw + 0.88rem, 1.125rem);
  font-weight: 500;
  color: var(--ink);
}

/* ----------------------------------------------------------- the lever ---
   A slot-machine handle in the gutter beside the wall of work. Hidden by
   default and shown only where there is both a drifting collage to spin and
   enough margin to sit in without crowding it. */

.lever {
  display: none;
  position: absolute;
  /* A comfortable target on a tablet; the graphic itself is narrower. */
  min-width: 2.75rem;
  min-height: 2.75rem;
  touch-action: manipulation;
  top: 50%;
  /* Hangs in the gutter beside the grid. Measured off the gutter rather than
     set to a fixed offset: the gutter is a vw clamp, so at 1280px a fixed
     -3.4rem put the knob three pixels past the edge of the window and gave
     the whole page a horizontal scrollbar. */
  right: calc(-1 * (var(--gutter) - 0.6rem));
  z-index: 2;
  margin-top: -3.5rem;
  padding: 0;
  border: 0;
  background: none;
  cursor: grab;
  -webkit-tap-highlight-color: transparent;
}

.lever__art {
  display: block;
  width: 2.6rem;
  height: auto;
  overflow: visible;
}

/* Pivots at the foot of the rod, the way the real thing does. */
.lever__arm {
  transform-box: view-box;
  transform-origin: 28px 156px;
  transition: transform 0.42s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.lever:hover .lever__arm { transform: rotate(7deg); }
.lever:active { cursor: grabbing; }

.lever.is-pulled .lever__arm {
  transform: rotate(74deg);
  transition-duration: 0.16s;
  transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
}

.lever:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 6px;
  border-radius: 4px;
}

/* Wherever the collage runs as columns there is a gutter to hang this in, so
   the lever tracks that same breakpoint rather than a separate one: desktop
   and tablets in landscape. Narrower layouts get the scrubber instead. */
@media (min-width: 64rem) {
  .lever { display: block; }
}

/* -------------------------------------------------------- the scrubber ---
   The reel's equivalent of the lever, for layouts with no gutter to hang one
   in. Held right the work flies right, held left it flies left, and letting
   go settles it back. Styled as a real range input so it keeps its keyboard
   behaviour and its touch target for nothing. */

.scrub {
  display: none;
  grid-area: scrub;
  margin-top: 0.1rem;
}

.scrub__input {
  display: block;
  width: 100%;
  height: 3rem;                   /* the touch target, not the bar */
  margin: 0;
  background: none;
  -webkit-appearance: none;
  appearance: none;
  touch-action: none;             /* so a drag scrubs instead of scrolling */
  cursor: grab;
}

.scrub__input:active { cursor: grabbing; }

.scrub__input::-webkit-slider-runnable-track {
  height: 0.5rem;
  border-radius: 0.25rem;
  background:
    linear-gradient(to bottom, rgba(255, 253, 249, 0.20) 0 38%, transparent 38%),
    var(--ink);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.4);
}

.scrub__input::-moz-range-track {
  height: 0.5rem;
  border-radius: 0.25rem;
  background:
    linear-gradient(to bottom, rgba(255, 253, 249, 0.20) 0 38%, transparent 38%),
    var(--ink);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.4);
}

.scrub__input::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 2.15rem;
  height: 2.15rem;
  margin-top: calc((0.5rem - 2.15rem) / 2);
  border: 1.5px solid rgba(47, 50, 68, 0.45);
  border-radius: 50%;
  background:
    radial-gradient(circle at 34% 29%, rgba(255, 255, 255, 0.5) 0 12%,
                    rgba(255, 255, 255, 0) 46%),
    var(--terracotta);
  box-shadow: 0 3px 7px -1px rgba(47, 50, 68, 0.5);
}

.scrub__input::-moz-range-thumb {
  width: 2.15rem;
  height: 2.15rem;
  border: 1.5px solid rgba(47, 50, 68, 0.45);
  border-radius: 50%;
  background:
    radial-gradient(circle at 34% 29%, rgba(255, 255, 255, 0.5) 0 12%,
                    rgba(255, 255, 255, 0) 46%),
    var(--terracotta);
  box-shadow: 0 3px 7px -1px rgba(47, 50, 68, 0.5);
}

.scrub__input:focus-visible { outline: 2px solid var(--teal); outline-offset: 4px; }

/* ------------------------------------------------------- the enquiry form */

.enquiry {
  padding-top: clamp(1.25rem, 2.8vh, 1.9rem);
  border-top: 1px solid var(--rule);
  max-width: 30rem;
}

.enquiry__title {
  margin: 0 0 0.9rem;
  font-size: 1.0625rem;
  font-style: italic;
  font-weight: 400;
  color: var(--ink);
}

.enquiry__row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.7rem;
}

.field { display: block; min-width: 0; }
.enquiry__row + .field { margin-top: 0.7rem; }

.field__label {
  display: block;
  margin-bottom: 0.25rem;
  font-size: 0.9375rem;
  font-weight: 400;
  color: var(--ink-faint);
}

.field__input {
  width: 100%;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--rule);
  border-radius: 3px;
  background: rgba(255, 253, 249, 0.66);
  color: var(--ink);
  font-family: var(--text);
  /* 16px keeps iOS Safari from zooming the page when a field takes focus. */
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.5;
  transition: border-color 0.16s ease, box-shadow 0.16s ease, background 0.16s ease;
}

textarea.field__input { resize: vertical; min-height: 4.5rem; }

.field__input:hover { border-color: #bfb2a0; }

.field__input:focus-visible {
  outline: none;
  border-color: var(--teal);
  background: #fffdf9;
  box-shadow: 0 0 0 3px rgba(51, 112, 122, 0.17);
}

.field__input[aria-invalid="true"] {
  border-color: var(--terracotta);
  box-shadow: 0 0 0 3px rgba(143, 64, 52, 0.14);
}

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

.enquiry__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.7rem 1rem;
  margin-top: 1.05rem;
}

.button {
  flex: none;
  min-height: 2.75rem;          /* a comfortable touch target */
  padding: 0.6rem 1.5rem;
  border: 1px solid var(--ink);
  border-radius: 2px;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--text);
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: 0.005em;
  cursor: pointer;
  transition: background 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
}

.button:hover { background: var(--terracotta); border-color: var(--terracotta); }
.button:active { transform: translateY(1px); }

.button:focus-visible { outline: 2px solid var(--teal); outline-offset: 3px; }
.button[disabled] { opacity: 0.55; cursor: progress; }

.enquiry__status {
  margin: 0;
  flex: 1 1 12rem;
  min-width: 0;
  font-size: 0.9375rem;
  font-style: italic;
  line-height: 1.45;
  color: var(--ink-soft);
}

.enquiry__status.is-error { color: var(--terracotta); }
.enquiry__status.is-ok { color: var(--teal); }
.enquiry__status a { color: inherit; }

/* ------------------------------------------------------------ the collage
   Two shapes, chosen by main.js and switched by class:

     .collage--v   columns drifting vertically   (laptop and up)
     .collage--h   one row drifting sideways     (phones, short landscapes)
     no class      plain masonry, holding still  (no JS, or reduced motion)

   Spacing lives on each plate's own margin rather than a flex gap, so that
   half of the doubled stack is exactly one cycle. A gap would leave the seam
   a notch off and the loop would visibly jump. */

.collage { min-width: 0; }

.collage--v,
.collage--h {
  display: flex;
  overflow: hidden;
}

.collage--v {
  flex-direction: row;
  gap: var(--plate-gap);
  align-items: flex-start;
  height: 40rem;
  height: min(84svh, 46rem);
  /* Dissolve the leading and trailing edges so the work reads as an endless
     wall rather than a box with things scrolling in it. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 9%, #000 91%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 9%, #000 91%, transparent 100%);
}

.collage--h {
  flex-direction: column;
  --reel-h: 15rem;
  --reel-h: clamp(12rem, 38svh, 18rem);
  height: var(--reel-h);
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 4.5rem, #000 calc(100% - 4.5rem), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 4.5rem, #000 calc(100% - 4.5rem), transparent 100%);
}

.collage__col {
  flex: 1 1 0;
  min-width: 0;
}

.collage__run { will-change: transform; }

.collage--v .collage__run {
  display: flow-root;
  animation: drift-y var(--drift, 60s) linear infinite;
}

.collage--h .collage__run {
  display: flex;
  height: 100%;
  /* Without this the run is capped at the column's width and the plates simply
     overflow it — which would make translateX(-50%) half a viewport, not half
     a reel, and the loop would jump. */
  width: max-content;
  animation: drift-x var(--drift, 60s) linear infinite;
}

.collage__run--reverse { animation-direction: reverse; }

.collage--v:hover .collage__run,
.collage:focus-within .collage__run { animation-play-state: paused; }

@keyframes drift-y {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(0, -50%, 0); }
}

@keyframes drift-x {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

/* Static fallback: no script, or someone who'd rather nothing moved. */
.collage:not(.collage--v):not(.collage--h) {
  columns: 2;
  column-gap: var(--plate-gap);
}

.collage:not(.collage--v):not(.collage--h) .plate { break-inside: avoid; }

.plate {
  position: relative;
  margin-bottom: var(--plate-gap);
  aspect-ratio: var(--plate-ratio, 1);
  background-color: var(--plate-tint, var(--paper-deep));
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  border-radius: 2px;
  box-shadow:
    0 1px 1px rgba(47, 50, 68, 0.07),
    0 12px 26px -16px rgba(47, 50, 68, 0.45);
  /* Discourage the easy grabs: no drag, no long-press sheet, no text select.
     Real protection is the baked-in watermark and the capped resolution. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  -webkit-user-drag: none;
  /* main.js sets --tilt from each plate's position within one copy of the run,
     so a plate and its loop duplicate lean the same way. Deriving it from
     :nth-child() instead would give the duplicate the opposite lean, and every
     plate would visibly twitch as the loop wrapped. */
  transform: rotate(var(--tilt, 0deg));
}

/* Both dimensions stated outright. Asking for height:100% and width:auto and
   letting aspect-ratio settle the rest reads fine and computes to zero width
   in WebKit inside a flex row, which empties the reel on exactly the devices
   it is built for. --plate-ratio is a bare number, so this is just arithmetic. */
.collage--h .plate {
  flex: none;
  height: var(--reel-h);
  width: calc(var(--reel-h) * var(--plate-ratio, 1));
  aspect-ratio: auto;
  margin-bottom: 0;
  margin-right: var(--plate-gap);
}

/* A transparent pane over each plate, so a click or long-press lands on an
   empty element instead of anything the browser would offer to save. */
.plate::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  /* A whisper of an inner edge, like the deckle on a sheet of cotton rag. */
  box-shadow: inset 0 0 0 1px rgba(47, 50, 68, 0.08);
}

/* No script, nothing looping — :nth-child() is safe here. */
.collage:not(.collage--v):not(.collage--h) .plate:nth-child(odd)  { --tilt: -0.55deg; }
.collage:not(.collage--v):not(.collage--h) .plate:nth-child(even) { --tilt: 0.5deg; }

.colophon {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 84rem;
  margin: clamp(1.25rem, 3vh, 2.25rem) auto 0;
  padding-bottom: max(clamp(1rem, 2.5vh, 1.75rem), env(safe-area-inset-bottom));
  font-size: 0.875rem;
  font-style: italic;
  color: var(--ink-faint);
  text-wrap: pretty;
}

/* ------------------------------------------------- one column, art on top
   Below this width the editorial column and the collage stop fitting side by
   side. The art moves directly under the headline: on a phone it is the thing
   worth seeing first, and the form follows it. */

@media (max-width: 63.99rem) {
  .hero__grid {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "intro" "art" "scrub" "contact";
    align-content: start;
    row-gap: clamp(1.5rem, 4vh, 2.5rem);
    max-width: 40rem;
    padding-top: clamp(0.5rem, 3vh, 2rem);
  }

  .lede, .enquiry { max-width: none; }
  .scrub { display: block; }

  /* Bleed the reel to the full width of the screen: the mask needs room to
     fade, and a hard edge inside the gutter would read as a cropped box. */
  .collage--h {
    width: 100vw;
    margin-inline: calc(50% - 50vw);
  }
}

@media (max-width: 30rem) {
  .enquiry__row { grid-template-columns: 1fr; gap: 0.6rem; }
  .enquiry__row + .field { margin-top: 0.6rem; }
}

/* --------------------------------------------------------- short viewports
   Landscape phones have height to spare nowhere and width everywhere. */

@media (max-height: 34rem) and (max-width: 63.99rem) {
  .hero { min-height: 0; }
  .collage--h {
    --reel-h: 11rem;
    --reel-h: clamp(8.5rem, 52svh, 13rem);
  }
}

/* ------------------------------------------------------------- preferences */

@media (prefers-reduced-motion: reduce) {
  /* Nothing is drifting, so there is nothing to spin or scrub. */
  .lever, .scrub { display: none !important; }
  .collage__run { animation: none; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

@media (prefers-contrast: more) {
  :root { --ink-soft: #3d4050; --ink-faint: #4b4e5d; --rule: #9c9384; }
  .field__input { background: #fffdf9; }
}

@media print {
  body::before, body::after, .collage, .enquiry { display: none; }
}
