/* ==========================================================================
   STONEGRAPH — the constellation
   --------------------------------------------------------------------------
   The layer itself. Everything that MOVES is set from constellation.js, because
   each element's delay is computed from its distance to the centre and there is
   no way to express "stagger by position" in a stylesheet. What lives here is
   what every element shares: the ground, the weight of a hairline, the colour
   of a plate, and the type.
   ========================================================================== */

.cn {
  position: relative;
  /* Layered: the words sit in the middle of the network, which is the whole
     point of the composition. They are kept apart by the type being small
     enough and the frame being tall enough — not by stacking them, which was
     tried and lost the effect. */
  display: grid;
  align-items: center;
  min-height: clamp(560px, 78vh, 820px);
  /* The band arrives inset and rounded and opens to full bleed as the section
     is entered — see .cn--closed. Driven by variables on a background LAYER
     rather than by transforming the section: a scaled section takes the text
     inside it along and softens every letter on the way in. */
  --cn-inset: 0px;
  --cn-round: 0px;
  /* The stage needs height or nothing works. The viewBox is 1440x760, and
     `slice` scales it to COVER the section — so in a section shorter than that
     ratio the top and bottom are cropped away and every node is pulled inward
     toward the text. Fixed by `meet` instead, which fits the whole coordinate
     space in and can never crop at any size.

     (The `min-height: 0` that sat here is gone. It was left behind by the
     stacked layout, and being later in this same rule it beat the height set
     above — so the band collapsed to 423px, the network was squeezed into it,
     and the nodes ended up back on top of the words.) */
  display: grid;
  align-items: center;
  /* The same near-black the footer stands on, so the two dark ends of the page
     are one colour rather than two that nearly match. */
  background: #141413;
  color: #F0EEE6;
  overflow: hidden;
  isolation: isolate;
}

/* The ground, drawn as its own layer so it can be inset and rounded without
   moving anything standing on it. */
.cn::before {
  content: "";
  position: absolute;
  inset: var(--cn-inset);
  border-radius: var(--cn-round);
  background: #141413;
  z-index: 0;
  transition: inset 1.1s cubic-bezier(.16,1,.3,1), border-radius 1.1s cubic-bezier(.16,1,.3,1);
}
/* Where it starts: a smaller rounded panel that opens out. 44px reads as
   "this grew" without leaving so much sand showing that the page looks
   unfinished while it waits to be scrolled to. */
.cn--closed { --cn-inset: 44px; --cn-round: 20px; }
@media (max-width: 860px) {
  .cn--closed { --cn-inset: 16px; --cn-round: 14px; }
}
@media (prefers-reduced-motion: reduce) and (prefers-reduced-motion: no-preference) { /* OFF by the motion policy — see motion-always.css */
  .cn::before { transition: none; }
  .cn--closed { --cn-inset: 0px; --cn-round: 0px; }
}

/* The network sits behind the words and is not interactive except for the hub
   links, which opt back in below. */
.cn__stage {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  grid-area: 1 / 1;
}
.cn__net {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* ---- the connectors ----
   A hairline at 1px and 22% is almost nothing, and that is the intent: the
   network should be felt at a glance and only resolve into lines when looked
   at. Brighter than this and the page becomes a diagram. */
.cn__line {
  stroke: #F0EEE6;
  stroke-opacity: 0.22;
  stroke-width: 1;
  fill: none;
  /* Sub-pixel geometry is the norm here — every endpoint is a computed centre —
     so let the renderer place the line where it really is rather than snapping
     it to a device pixel and bending the run. */
  shape-rendering: geometricPrecision;
  vector-effect: non-scaling-stroke;
}

/* ---- the plates ----
   The stone tone is the same --stone-150 the site mats its photographs on, at
   the strength a mounting board would read at against near-black. A plate with
   no photograph in it is not a placeholder waiting to be filled; it is a piece
   of stone that has not been carved yet, which is the right thing for a studio
   rebuilding its portfolio to be showing. */
.cn__plate {
  /* Transforms are written as a matrix around each plate's own centre, so the
     origin has to be the coordinate space rather than the box. */
  transform-box: view-box;
  transform-origin: 0 0;
}
.cn__plate-bg {
  fill: #DCD5C6;
  fill-opacity: 0.9;
}
.cn__plate-img {
  /* Sits over the plate, so a photograph that is still loading shows stone
     rather than a hole. */
  opacity: 0.96;
}

/* ---- the hubs ---- */
.cn__hub {
  pointer-events: auto;
  cursor: pointer;
}
.cn__hub-t {
  font-family: var(--f-display, Georgia, serif);
  font-size: 21px;
  font-weight: 400;
  letter-spacing: 0.005em;
  fill: #F0EEE6;
  transition: fill .35s ease;
}
.cn__hub:hover .cn__hub-t { fill: var(--accent-dark, #C2AA7E); }
.cn__hub:focus-visible .cn__hub-t { fill: var(--accent-dark, #C2AA7E); }
.cn__hub:focus-visible { outline: none; }

/* ---- the words in the middle ----
   Given a ground of their own: at 1px and 22% the hairlines are quiet, but a
   line crossing behind a letterform still costs legibility, and this is the one
   thing on the page that has to be read. A radial wash rather than a box, so
   the network fades out under the text instead of stopping at an edge. */
.cn__inner {
  position: relative;
  z-index: 2;
  grid-area: 1 / 1;
  /* Narrower than the page measure on purpose: the headline has to sit inside
     the clearing the network leaves it, and a 1152px block reaches into the
     nodes on both sides. */
  max-width: 560px;
  margin-inline: auto;
  padding: clamp(32px, 5vh, 64px) clamp(24px, 4vw, 40px);
  text-align: center;
  grid-area: 1 / 1;
}
/* A ground of their own. At 1px and 22% the hairlines are quiet, but a line
   crossing behind a letter still costs legibility, and this is the one thing
   here that has to be read. A radial wash rather than a box, so the network
   fades out under the text instead of stopping at an edge. */
.cn__inner::before {
  content: "";
  position: absolute;
  left: 50%; top: 50%;
  width: min(760px, 122%); height: min(440px, 128%);
  transform: translate(-50%, -50%);
  background: radial-gradient(closest-side, #141413 0%, rgba(20,20,19,0.9) 46%, rgba(20,20,19,0) 100%);
  pointer-events: none;
  z-index: -1;
}

.cn__title {
  font-family: var(--f-display, Georgia, serif);
  /* Was clamp(2.4rem, 5.6vw, 4.4rem) — 70px at 1440, which wanted 660px of
     width for two lines and ran into the network on both sides. This fits the
     560px clearing with room to spare at every width. */
  font-size: clamp(1.75rem, 3.4vw, 2.85rem);
  /* 1.04 was tighter than the face itself: at 70px the ascenders and
     descenders overflowed their line box by 8px, which is a clip waiting for
     any ancestor that sets overflow:hidden — and the section above does. 1.12
     is still tight for display type and the glyphs stay inside the box. */
  line-height: 1.12;
  letter-spacing: -0.015em;
  color: #F0EEE6;
  margin: 0;
}
.cn__sub {
  margin: clamp(18px, 2vw, 26px) auto 0;
  max-width: 52ch;
  font-size: clamp(0.95rem, 1.2vw, 1.06rem);
  line-height: 1.65;
  color: rgba(240, 238, 230, 0.62);
}
.cn__act { margin-top: clamp(24px, 3vw, 36px); }

/* ---- narrower screens ----
   The layout is a viewBox, so it scales rather than reflows — but `slice` on a
   tall, narrow viewport crops the sides away and takes the outer plates with
   them. Below a tablet the band is given a fixed ratio of its own and the whole
   network is fitted inside it, so nothing is lost; the words move below it
   instead of sitting over it, because at that size there is not enough room to
   do both without one obscuring the other. */
@media (max-width: 860px) {
  /* A phone is the one place stacking is right: 375px has no middle to keep
     clear, so the network takes its own band and the words sit under it. */
  .cn { display: flex; flex-direction: column; min-height: 0; }
  .cn__stage {
    position: relative;
    inset: auto;
    width: 100%;
    aspect-ratio: 620 / 880;
  }
  .cn__inner::before { display: none; }
  .cn__inner {
    padding-top: clamp(28px, 5vh, 48px);
    padding-bottom: clamp(56px, 9vh, 90px);
  }
  .cn__inner::before { display: none; }
  .cn__hub-t { font-size: 26px; }
}

@media (prefers-reduced-motion: reduce) and (prefers-reduced-motion: no-preference) { /* OFF by the motion policy — see motion-always.css */
  .cn__hub-t { transition: none; }
}


/* ==========================================================================
   FOUR WAYS IN — the card grid under the hero
   --------------------------------------------------------------------------
   Four doors into the site, on the studio's own sand rather than a panel of
   its own: the reference sets these on the page colour and lets a hairline and
   a barely-there warm fill do the separating, which is why they read as places
   to go rather than as boxes. A card with a heavy background becomes a tile;
   a card with almost none stays a card.

   The bronze is spent in exactly two places per card — the small mark and the
   link at the bottom — and nowhere else. That restraint is the whole reason
   the grid reads as calm: four cards with a coloured heading each would be a
   dashboard.
   ========================================================================== */

.ways__head { margin-bottom: clamp(30px, 3.6vw, 52px); }
.ways__head .eyebrow { display: flex; align-items: center; gap: 9px; }

/* The wordmark, at eyebrow size, masked in the accent — the same treatment the
   assistant's launcher uses, so the mark is one thing across the site. */
.ways__mark,
.way__mark {
  display: inline-block;
  width: 15px; height: 17px;
  flex: none;
  background: var(--accent);
  -webkit-mask: var(--mark-src) center / contain no-repeat;
  mask: var(--mark-src) center / contain no-repeat;
}
.way__mark { width: 17px; height: 19px; margin-bottom: 12px; }

/* The italic tail of the heading, in the accent. One coloured phrase in the
   section, on the word the sentence turns on. */
.ways__head em {
  font-style: italic;
  color: var(--accent);
}

.ways__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(14px, 1.6vw, 22px);
}

.way {
  display: block;
  padding: clamp(22px, 2.4vw, 32px);
  border: 1px solid var(--rule);
  border-radius: 14px;
  /* Barely there on purpose: enough to lift the card off the sand, not enough
     to become a panel. */
  background: rgba(255, 253, 247, 0.55);
  transition: background-color .4s var(--ease), border-color .4s var(--ease),
              transform .5s var(--ease);
}
.way:hover {
  background: rgba(255, 253, 247, 0.92);
  border-color: rgba(111, 90, 60, 0.28);
  transform: translateY(-3px);
}

.way__t {
  font-family: var(--f-display);
  font-size: clamp(1.16rem, 1.6vw, 1.4rem);
  line-height: 1.2;
  color: var(--ink);
  margin: 0 0 8px;
}
.way__d {
  font-size: clamp(.92rem, 1.05vw, 1rem);
  line-height: 1.65;
  color: var(--ink-mute);
  margin: 0;
  /* A measure, so the second line does not run the full width of the card and
     leave the third stranded. */
  max-width: 42ch;
}
.way__go {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: clamp(16px, 1.8vw, 22px);
  font-family: var(--f-mono);
  font-size: .6rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--accent);
  transition: gap .35s var(--ease);
}
.way:hover .way__go { gap: 13px; }

@media (max-width: 760px) {
  .ways__grid { grid-template-columns: 1fr; }
  /* The lift is a pointer gesture. On a phone it fires on tap and reads as the
     card flinching away from the finger. */
  .way:hover { transform: none; }
  .way__go { font-size: .68rem; }
}

/* ==========================================================================
   THE CONSTELLATION, ON THE HOME PAGE
   Additions to constellation.css for the eyebrow the standalone preview does
   not carry.
   ========================================================================== */
.cn__eyebrow {
  font-family: var(--f-mono);
  font-size: .6rem;
  letter-spacing: .24em;
  text-transform: uppercase;
  /* Was 0.45 alpha, which is 4.06:1 on this ground — under the 4.5:1 floor for
     text this size, and this label is 11px mono at a quarter em of tracking,
     which is the least forgiving setting on the site. 0.58 reads as the same
     quiet label and clears the floor. */
  color: rgba(240, 238, 230, 0.58);
  margin: 0 0 clamp(14px, 1.6vw, 20px);
}


/* ---- the float ----
   Applied to the inner group of each plate, with its duration and phase set
   per plate from constellation.js so no two are ever in step. Transform only,
   so twenty-five of them run on the compositor and cost nothing.

   4px against a 760-unit viewBox is about half a percent of the height: not
   enough to read as movement when you look at any one plate, and enough that
   the network is never quite still. That is the intent — alive, not animated. */
.cn__drift {
  animation-name: cn-float;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-direction: alternate;
  transform-box: view-box;
  transform-origin: 0 0;
}
.cn__net {
  /* The amplitude, as variables, because it cannot be a constant.

     These are viewBox units. On a laptop the drawing is fitted at roughly 1:1
     so a 9-unit drift is a 9px drift and reads as breathing. On a phone the
     same 1440-unit space is fitted into 375px — a scale of 0.26 — so the
     identical keyframe produced a drift of about two pixels. That is the
     "motion is so low it is not visible" report: it was not low, it was being
     divided by four.

     constellation.js multiplies these by the same zoom it uses for the plates
     and the labels, so the movement stays in proportion to the pieces that are
     moving, at every size.

     The amplitude is also a layout constraint. Two neighbouring plates drift on
     independent clocks, so at the worst moment they close 2x --cn-dy on each
     other; GAP_Y in constellation.js must stay above that, and it does — the
     gaps are scaled by the same zoom. Change one and change the other. */
  --cn-dx: 5px;
  --cn-dy: 12px;
}
/* Declared on the SVG, not on .cn__drift, and read with a fallback.

   Put on the drift element itself these defaults sat closer to it than the
   value constellation.js writes onto the <svg>, so the custom property never
   inherited down and the zoomed amplitude was silently ignored — the phone
   went on drifting two pixels. The plate reads the inherited value; the
   fallback is what a page with no script would use. */
@keyframes cn-float {
  from { transform: translate(calc(var(--cn-dx, 5px) * -1), calc(var(--cn-dy, 12px) * -1)) rotate(-0.55deg); }
  to   { transform: translate(var(--cn-dx, 5px), var(--cn-dy, 12px)) rotate(0.55deg); }
}
@media (prefers-reduced-motion: reduce) and (prefers-reduced-motion: no-preference) { /* OFF by the motion policy — see motion-always.css */
  .cn__drift { animation: none; }
}
