/* ============================================================
   product.css — published product pages and the product index.

   This file contains NO product colours. Every page carries its own
   palette as inline custom properties on <html>, computed in OKLCH by
   the operations console from two axes:

     the DEPTH  it was published at  -> how light or dark it is
     the CURRENT it was given        -> its hue, which is what makes
                                        one product look independent
                                        of the next

   Computing them means the pair is validated rather than guessed —
   `npm run palette` in ops/ checks text contrast and how far apart two
   products land, including under colourblindness. Hard-coding colours
   here would put them beyond the reach of that check, which is why the
   values below are only fallbacks for a page rendered without them.

   Unlike the rest of the site these pages do NOT follow the viewer's
   light/dark preference: the chosen depth IS the theme, and a product
   placed at Bedrock is meant to be dark for everyone.
   ============================================================ */

:root {
  /* Fallbacks only — a published page overrides every one of these inline. */
  --p-bg: #BCD1CC; --p-bg-2: #A5BFB9; --p-edge: rgba(9, 30, 34, .22);
  --p-ink: #08222A; --p-ink-2: #2F5158; --p-ink-3: #4F7078;
  --p-panel: rgba(255, 255, 255, .48); --p-glow: rgba(12, 80, 70, .16);
  --p-accent: #17705C;
  --p-measure: 34rem;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background:
    radial-gradient(120% 60% at 50% 0%, var(--p-glow), transparent 70%),
    linear-gradient(var(--p-bg), var(--p-bg-2));
  background-attachment: fixed;
  color: var(--p-ink);
  font: var(--t-body, 1.0425rem)/1.62 var(--f-body, system-ui, sans-serif);
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
  display: flex; flex-direction: column;
}

a { color: var(--p-accent); }

/* ---------- masthead & footer ---------- */
.p-masthead {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; padding: 1.15rem clamp(1.1rem, 5vw, 3.4rem);
  border-bottom: 1px solid var(--p-edge);
}
.p-wordmark { display: flex; align-items: center; gap: .55rem; text-decoration: none; color: var(--p-ink); }
.p-wordmark svg { width: 22px; height: 22px; color: var(--p-accent); }
.p-wordmark span {
  font-family: var(--f-display, sans-serif); text-transform: uppercase;
  letter-spacing: .04em; font-size: .96rem;
}
.p-wordmark i { font-style: normal; color: var(--p-ink-3); }
.p-masthead nav { display: flex; gap: 1.2rem; }
.p-masthead nav a {
  font-family: var(--f-mono, monospace); font-size: .66rem; letter-spacing: .18em;
  text-transform: uppercase; text-decoration: none; color: var(--p-ink-2);
}
.p-masthead nav a:hover { color: var(--p-accent); }

.p-footer {
  margin-top: auto;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 1.4rem clamp(1.1rem, 5vw, 3.4rem);
  border-top: 1px solid var(--p-edge);
  font-family: var(--f-mono, monospace); font-size: .68rem;
  letter-spacing: .1em; color: var(--p-ink-3);
}
.p-footer a { text-decoration: none; }

/* ---------- shared page furniture ---------- */
.p-wrap { padding: clamp(2.6rem, 7vw, 5.5rem) clamp(1.1rem, 5vw, 3.4rem); flex: 1; }

.p-eyebrow {
  margin: 0 0 1.1rem;
  font-family: var(--f-mono, monospace); font-size: .66rem;
  letter-spacing: .2em; text-transform: uppercase; color: var(--p-ink-3);
}
.p-eyebrow a { text-decoration: none; }
.p-eyebrow s { text-decoration: none; opacity: .45; margin: 0 .4rem; }
.p-eyebrow .p-depth { color: var(--p-accent); }

.p-hero h1 {
  margin: 0; font-family: var(--f-display, sans-serif); font-weight: 600;
  line-height: .94; letter-spacing: -.015em;
  font-size: clamp(2.8rem, 8vw, 6rem); text-transform: uppercase;
}
.p-lede {
  margin: 1.1rem 0 0; max-width: var(--p-measure);
  font-size: clamp(1.05rem, 1.5vw, 1.28rem); color: var(--p-ink-2); line-height: 1.5;
}

.p-cta {
  display: inline-flex; align-items: center; gap: .5rem; margin-top: 1.6rem;
  padding: .7rem 1.25rem; border-radius: 3px; text-decoration: none;
  background: var(--p-accent); color: var(--p-bg);
  font-weight: 600; font-size: .92rem;
}
.p-cta svg { width: 15px; height: 15px; }
.p-cta:hover { filter: brightness(1.08); }

.p-cta-note {
  margin: .5rem 0 0; font-family: var(--f-mono, monospace); font-size: .74rem;
  letter-spacing: .04em; color: var(--p-ink-3);
}

.p-body { max-width: var(--p-measure); margin-top: 2.4rem; }
.p-body p { margin: 0 0 1.1rem; color: var(--p-ink-2); }

.p-highlights { list-style: none; margin: 1.8rem 0 0; padding: 0; display: grid; gap: .55rem; }
.p-highlights li { position: relative; padding-left: 1.4rem; color: var(--p-ink); }
.p-highlights li::before {
  content: ""; position: absolute; left: 0; top: .62em;
  width: .5rem; height: .5rem; border: 1px solid var(--p-accent); transform: rotate(45deg);
}

.p-facts {
  margin-top: 2.6rem; padding: 1rem 1.1rem;
  border: 1px solid var(--p-edge); border-radius: 3px; background: var(--p-panel);
  display: grid; gap: .5rem; max-width: 22rem;
}
.p-facts span { display: flex; justify-content: space-between; gap: 1rem; font-size: .86rem; color: var(--p-ink-2); }
.p-facts b {
  font-family: var(--f-mono, monospace); font-weight: 500; font-size: .62rem;
  letter-spacing: .16em; text-transform: uppercase; color: var(--p-ink-3);
}

/* ---------- layouts ---------- */
.p-split { display: grid; gap: clamp(2rem, 5vw, 4rem); grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; }
.p-split .p-body { margin-top: 0; }
.p-split .p-facts { max-width: none; }

.p-full .p-hero h1 { font-size: clamp(3.2rem, 13vw, 10rem); }
.p-full .p-cols { display: grid; gap: clamp(2rem, 5vw, 4rem); grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); align-items: start; margin-top: 1rem; }
.p-full .p-facts { margin-top: 2.4rem; max-width: none; }

@media (max-width: 780px) {
  .p-split, .p-full .p-cols { grid-template-columns: 1fr; }
}

/* ---------- index ---------- */
.p-index-hero { margin-bottom: clamp(2.2rem, 5vw, 3.6rem); }
.p-index-hero h1 { font-size: clamp(2.4rem, 6.5vw, 4.6rem); }

.p-grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr)); }

/* Each card previews its product's own zone, so the index reads as a
   cross-section of the water rather than a uniform list. */
.p-card {
  display: grid; gap: .5rem; align-content: start;
  padding: 1.3rem 1.35rem 1.15rem; text-decoration: none; border-radius: 3px;
  background: linear-gradient(var(--p-bg), var(--p-bg-2));
  border: 1px solid var(--p-edge); color: var(--p-ink);
  min-height: 12.5rem;
}
.p-card:hover { border-color: var(--p-accent); }
.p-card-depth {
  font-family: var(--f-mono, monospace); font-size: .6rem; letter-spacing: .18em;
  text-transform: uppercase; color: var(--p-accent);
}
.p-card b {
  font-family: var(--f-display, sans-serif); font-weight: 600; font-size: 1.7rem;
  line-height: 1; text-transform: uppercase; letter-spacing: -.01em;
}
.p-card-line { color: var(--p-ink-2); font-size: .9rem; line-height: 1.45; }
.p-card-foot { margin-top: auto; display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; padding-top: .6rem; }
.p-chip {
  font-family: var(--f-mono, monospace); font-size: .58rem; letter-spacing: .12em;
  text-transform: uppercase; padding: .2rem .5rem; border-radius: 999px;
  border: 1px solid var(--p-edge); color: var(--p-ink-3);
}
.p-card-go {
  margin-left: auto; font-family: var(--f-mono, monospace); font-size: .62rem;
  letter-spacing: .08em; color: var(--p-accent); opacity: .55;
  transform: translateX(0); transition: opacity 200ms ease, transform 200ms ease;
}
.p-card:hover .p-card-go, .p-card:focus-visible .p-card-go { opacity: 1; transform: translateX(3px); }
.p-empty { color: var(--p-ink-3); padding: 3rem 0; }

/* ---------- preview flag ---------- */
.p-preview-flag {
  margin: 0; padding: .5rem 1rem; text-align: center;
  background: #D6A85C; color: #1a1205;
  font-family: var(--f-mono, monospace); font-size: .64rem;
  letter-spacing: .18em; text-transform: uppercase; font-weight: 600;
}

/* ---------- the image stack ----------
   One to three images as plates suspended in 3D space, each hanging a little
   further back and lower than the one before it — the same descent the rest
   of the site is built on.

   Three states, and they layer:

     at rest             a stack, receding into the water
     stack hovered       the plates level out and fan apart
     one plate hovered   that plate comes forward, grows, shows its caption

   Sized by HEIGHT rather than width. Product shots are usually portrait phone
   screenshots, and a width-driven plate turns those into something several
   screens tall; fixing the height instead means a portrait shot and a wide
   desktop capture both occupy the same band of the page.

   Entirely CSS: a published page ships no JavaScript, so the depth is real
   perspective rather than a script reading pointer positions. */

.p-stack {
  --plate-h: clamp(20rem, 40vw, 34rem);
  height: var(--plate-h);
  /* Hug the plates. A full-width stack puts perspective-origin at the centre
     of the whole column while the plates sit at its left, so pushing a plate
     toward the reader magnifies it about a point far to its right and throws
     its left edge off-screen. Sizing the box to its contents keeps the
     vanishing point where the plates actually are. */
  width: fit-content;
  max-width: 100%;
  margin: 2.8rem 0 3.4rem;
  padding: 0 9rem 3rem 0;      /* room for the plates to fan and grow into */
  /* The perspective origin sits above centre so plates lean away from the
     reader's eye-line rather than tipping toward the floor. */
  perspective: 1600px;
  perspective-origin: 50% 30%;
  display: grid;
  /* All plates share one row, and that row is pinned to the container's
     height. Without an explicit track the implicit row is `auto`, sizes itself
     to the tallest image and overflows — a phone screenshot then renders at
     its natural 1400px and swallows the page. minmax(0, 1fr) is what makes
     `height: 100%` on the image resolve against something definite. */
  grid-template-rows: minmax(0, 1fr);
  justify-items: start;
}

.p-plate {
  grid-area: 1 / 1;                     /* all plates share one cell: a stack */
  position: relative;
  display: block;
  height: 100%;
  cursor: pointer;
  outline: none;
  /* Deliberately NOT overflow:hidden. Clipping a plate that is translated far
     along Z makes Chrome rasterise it into a composited layer that renders
     washed out, with the plates behind showing through. It is not needed
     either: the caption only appears while its own plate is hovered, and that
     state straightens the plate to rotateY(0), so nothing projects past the
     image edge to clip. */
  transform-style: preserve-3d;
  /* Each plate is pushed back in z, dropped slightly, and turned a few degrees.
     --i is set per plate by the renderer, so the same rule stages all three. */
  transform:
    translate3d(calc(var(--i) * 2.4rem), calc(var(--i) * 1rem), calc(var(--i) * -7rem))
    rotateY(-14deg) rotateX(3deg) rotateZ(calc(var(--i) * -0.8deg));
  transform-origin: 15% 50%;
  transition: transform .5s cubic-bezier(.22, .61, .36, 1), filter .4s ease;
  z-index: calc(10 - var(--i));
}

.p-plate img {
  display: block;
  height: 100%;
  width: auto;
  max-width: none;
  border-radius: 3px;
  border: 1px solid var(--p-edge);
  /* The shadow falls down and away, which is what actually sells the depth —
     the transform alone reads as a flat rotation. */
  box-shadow:
    0 1px 1px rgba(0, 0, 0, .16),
    calc(var(--i) * 0.3rem + 0.7rem) calc(var(--i) * 0.4rem + 1rem)
      calc(var(--i) * 0.8rem + 2rem) rgba(0, 0, 0, .26);
  background: var(--p-panel);
  transition: box-shadow .5s cubic-bezier(.22, .61, .36, 1);
}

/* A single image is not a stack; it gets a gentler tilt and no offset. */
.p-stack[data-count="1"] .p-plate { transform: rotateY(-9deg) rotateX(2deg); }

/* --- state 2: the stack is hovered, so fan the plates apart ---
   Each steps right by roughly its own visible width. This is what gives the
   next state something to aim at: until they separate there is no individual
   plate to point at. */
.p-stack:hover .p-plate,
.p-stack:focus-within .p-plate {
  transform: translate3d(calc(var(--i) * 6.5rem), 0, calc(var(--i) * -1.2rem))
             rotateY(-4deg) rotateX(0deg);
}
.p-stack[data-count="1"]:hover .p-plate,
.p-stack[data-count="1"]:focus-within .p-plate {
  transform: rotateY(-3deg) translate3d(0, -0.3rem, 1.5rem);
}

/* --- state 3: one plate is chosen, so bring it out of the stack ---
   translateZ rather than scale(): with a perspective set, moving the plate
   toward the reader is what actually makes it bigger, and it grows the way an
   object approaching the eye does rather than being flatly enlarged.

   The selectors are doubled up (.p-stack:hover .p-plate:hover) because the
   fan rule above is two classes plus a pseudo-class, and a bare
   .p-plate:hover would lose to it on specificity and never apply. */
.p-stack:hover .p-plate:hover,
.p-stack:focus-within .p-plate:focus-visible,
.p-stack[data-count="1"]:hover .p-plate:hover,
.p-stack[data-count="1"]:focus-within .p-plate:focus-visible {
  transform: translate3d(calc(var(--i) * 6.5rem), -1rem, 10rem) rotateY(0deg) rotateX(0deg);
  z-index: 30;
}
.p-stack:hover .p-plate:hover img,
.p-stack:focus-within .p-plate:focus-visible img {
  box-shadow: 0 2rem 4rem rgba(0, 0, 0, .42), 0 0 0 1px var(--p-accent);
}

/* Everything else steps back and dims, so the chosen plate is the only thing
   in focus. :has() does the work; where it is unsupported the other plates
   simply do not dim, which costs nothing. */
.p-stack:has(.p-plate:hover) .p-plate:not(:hover),
.p-stack:has(.p-plate:focus-visible) .p-plate:not(:focus-visible) {
  filter: brightness(.72) saturate(.85);
}

/* The caption is the image's own alt text — the description already written
   for it in the console. Hidden until its plate is the one chosen. */
.p-plate-note {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  margin: 0;
  padding: 1.8rem .9rem .75rem;
  font-size: .8rem;
  line-height: 1.38;
  color: #fff;
  /* The full text stays in the image's alt attribute either way, so trimming
     the visible caption loses nothing for a screen reader. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
  background: linear-gradient(transparent, rgba(0, 0, 0, .85));
  border-radius: 0 0 3px 3px;
  opacity: 0;
  transform: translateY(.4rem);
  transition: opacity .35s ease, transform .35s ease;
  pointer-events: none;
}
.p-plate:hover .p-plate-note,
.p-plate:focus-visible .p-plate-note {
  opacity: 1;
  transform: translateY(0);
}

.p-plate:focus-visible img { outline: 2px solid var(--p-accent); outline-offset: 3px; }

/* The split layout gives the stack a narrower column, so it leans less far. */
.p-split .p-stack { padding-right: 6rem; --plate-h: clamp(17rem, 33vw, 27rem); }

@media (max-width: 780px) {
  .p-stack {
    --plate-h: clamp(15rem, 62vw, 23rem);
    perspective: 1000px;
    padding-right: 4rem;
  }
  .p-plate {
    transform: translate3d(calc(var(--i) * 1.5rem), calc(var(--i) * 0.6rem),
                           calc(var(--i) * -4rem)) rotateY(-9deg);
  }
  .p-stack:hover .p-plate,
  .p-stack:focus-within .p-plate {
    transform: translate3d(calc(var(--i) * 3rem), 0, calc(var(--i) * -1rem)) rotateY(-4deg);
  }
  .p-stack:hover .p-plate:hover,
  .p-stack:focus-within .p-plate:focus-visible {
    transform: translate3d(calc(var(--i) * 3rem), -.5rem, 5rem) rotateY(0deg);
  }
}

/* The depth is decorative; the images are not. Reduced motion flattens the
   stack into a plain column, and the captions become permanent rather than
   appearing on hover — the detail stays reachable without the movement. */
@media (prefers-reduced-motion: reduce) {
  .p-stack {
    height: auto;
    perspective: none;
    padding: 0;
    display: grid;
    gap: 1.4rem;
    justify-items: stretch;
  }
  .p-plate,
  .p-stack:hover .p-plate,
  .p-stack:hover .p-plate:hover,
  .p-stack:focus-within .p-plate,
  .p-stack[data-count="1"] .p-plate {
    grid-area: auto;
    height: auto;
    transform: none;
    transition: none;
    filter: none;
  }
  .p-plate img {
    height: auto;
    width: 100%;
    max-width: min(100%, 26rem);
    box-shadow: 0 1px 2px rgba(0, 0, 0, .18);
  }
  .p-plate-note {
    position: static;
    opacity: 1;
    transform: none;
    display: block;
    overflow: visible;
    padding: .5rem 0 0;
    max-width: min(100%, 26rem);
    color: var(--p-ink-2);
    background: none;
  }
}
