/* ==========================================================================
   MixedMind Studios — mixedmindstudios.com
   Static site, no build step. See DESIGN.md for the design brief.

   Structure:
   1. Tokens
   2. Reset + base
   3. Accessibility (skip link, focus, reduced motion)
   4. Layout primitives
   5. The mix-seam (signature element)
   6. Header / nav
   7. Sections: hero, philosophy, services, portfolio, contact
   8. Footer
   9. Motion / reveal
   ========================================================================== */

/* 1. Tokens ---------------------------------------------------------------- */

:root {
  /* Palette */
  --ink:      #14151C;   /* near-black, warm-cool blue-black base            */
  --ink-lift: #1C1D26;   /* one step up from ink, for inset blocks           */
  --bone:     #EFEAE0;   /* warm off-white, light sections                   */
  --bone-sink:#E5DFD2;   /* one step down from bone, for inset blocks        */
  --indigo:   #3D2FE0;   /* cool half of the mix                             */
  --amber:    #FF8A3D;   /* warm half of the mix                             */
  --graphite: #6B6B70;   /* muted meta on light                              */
  --graphite-deep: #55555B; /* body copy on light (AA at normal sizes)       */
  --mist:     #B8B4AC;   /* muted text on dark                               */

  /* Per-section theming. Sections override these; everything else reads them.
     Rule: accent is AMBER on ink, INDIGO on bone — indigo fails contrast on
     ink, amber fails contrast on bone, so each ground gets its own half. */
  --bg:     var(--ink);
  --fg:     var(--bone);
  --fg-mute:var(--mist);
  --accent: var(--amber);
  --rule:   rgba(239, 234, 224, 0.14);

  /* Type */
  --display: 'MacronPatch', 'Fraunces', 'Iowan Old Style', Georgia, 'Times New Roman', serif;
  --body:    'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
  --mono:    'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Fluid type scale */
  --t-hero:  clamp(2.75rem, 1.4rem + 6.2vw, 7rem);
  --t-xl:    clamp(2rem, 1.2rem + 3.4vw, 4rem);
  --t-lg:    clamp(1.5rem, 1.1rem + 1.8vw, 2.375rem);
  --t-md:    clamp(1.125rem, 1.02rem + 0.5vw, 1.375rem);
  --t-body:  clamp(1rem, 0.96rem + 0.2vw, 1.125rem);
  --t-mono:  clamp(0.6875rem, 0.66rem + 0.14vw, 0.8125rem);

  /* Space */
  --gutter:  clamp(1.25rem, 5vw, 5rem);
  --section: clamp(4.5rem, 10vw, 9.5rem);
  --measure: 62ch;
  --max:     1240px;

  /* Motion */
  --ease: cubic-bezier(0.22, 0.68, 0.28, 1);
}

/* Fraunces ships a broken Ō (U+014C): the macron is dropped entirely, at every
   combination of the opsz/wght/SOFT/WONK axes. That would misspell KŌOPE — a
   brand name — everywhere it appears. This face is scoped to the two macron-O
   codepoints only and listed FIRST in the display stack, so those characters
   come from a system serif and every other character still comes from
   Fraunces. If none of the local fonts exist, it falls through to Fraunces and
   we are no worse off than before. */
@font-face {
  font-family: 'MacronPatch';
  src: local('Iowan Old Style'), local('Georgia'), local('Times New Roman'), local('Liberation Serif');
  unicode-range: U+014C, U+014D;
}

/* Fraunces variable-axis presets. SOFT rounds the terminals, WONK enables the
   wonky/quirky alternates — this is what keeps it from reading as generic
   high-contrast serif. */
.u-wonk   { font-variation-settings: 'SOFT' 40, 'WONK' 1, 'opsz' 120; }
.u-nowonk { font-variation-settings: 'SOFT' 20, 'WONK' 0, 'opsz' 40; }

/* 2. Reset + base ---------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 5rem;
}

body {
  background: var(--ink);
  color: var(--bone);
  font-family: var(--body);
  font-size: var(--t-body);
  font-weight: 400;
  line-height: 1.6;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, svg { display: block; max-width: 100%; }
a { color: inherit; }
button { font: inherit; color: inherit; background: none; border: 0; }

h1, h2, h3 {
  font-family: var(--display);
  font-weight: 400;
  line-height: 1.02;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

p { text-wrap: pretty; }

/* 3. Accessibility --------------------------------------------------------- */

.skip-link {
  position: fixed;
  top: 0.75rem;
  left: 0.75rem;
  z-index: 999;
  padding: 0.7rem 1rem;
  background: var(--amber);
  color: var(--ink);
  font-family: var(--mono);
  font-size: var(--t-mono);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  text-decoration: none;
  transform: translateY(-200%);
}
.skip-link:focus-visible,
.skip-link:focus { transform: none; }

/* Every interactive element gets the same ring, coloured by its section. */
:where(a, button, input, textarea, select, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 1px;
}
/* Fallback for engines without :focus-visible */
@supports not selector(:focus-visible) {
  :where(a, button, input, textarea, select):focus {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
  }
}

.sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* 4. Layout primitives ----------------------------------------------------- */

.section {
  background: var(--bg);
  color: var(--fg);
  padding-block: var(--section);
}
.section--bone {
  --bg: var(--bone);
  --fg: var(--ink);
  --fg-mute: var(--graphite-deep);
  --accent: var(--indigo);
  --rule: rgba(20, 21, 28, 0.14);
}
.section--ink {
  --bg: var(--ink);
  --fg: var(--bone);
  --fg-mute: var(--mist);
  --accent: var(--amber);
  --rule: rgba(239, 234, 224, 0.14);
}
/* One step up from ink. Used where two ink sections sit next to each other and
   the second needs to separate itself without breaking to bone. */
.section--ink-lift {
  --bg: var(--ink-lift);
  --fg: var(--bone);
  --fg-mute: var(--mist);
  --accent: var(--amber);
  --rule: rgba(239, 234, 224, 0.14);
}

.wrap {
  width: 100%;
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.eyebrow {
  font-family: var(--mono);
  font-size: var(--t-mono);
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--accent);
}

.section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.75rem 2rem;
  margin-bottom: clamp(2.5rem, 6vw, 4.5rem);
  padding-bottom: 1.25rem;
  border-bottom: 1px solid var(--rule);
}
.section-head h2 {
  font-size: var(--t-xl);
  flex: 1 1 14ch;
}
.section-head .eyebrow { flex: 0 0 auto; }

.lede {
  font-size: var(--t-md);
  line-height: 1.55;
  color: var(--fg-mute);
  max-width: var(--measure);
}

/* Text link with an underline that grows on hover — the only link decoration
   used site-wide, so hover/focus reads consistently everywhere. */
.link {
  color: var(--accent);
  text-decoration: none;
  font-family: var(--mono);
  font-size: var(--t-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
  padding-block: 0.25em;
  border-bottom: 1px solid currentColor;
  transition: gap 0.3s var(--ease), opacity 0.3s var(--ease);
}
.link:hover { gap: 1.1em; opacity: 0.82; }
.link .arrow { transition: transform 0.3s var(--ease); }
.link:hover .arrow { transform: translateX(2px); }

/* 5. The mix-seam ---------------------------------------------------------- */
/* The signature element. Real halftone: 40 dot patterns of stepping radius
   laid across the band, amber dots growing over indigo on the left half and
   indigo dots shrinking over amber on the right, meeting at exactly 50/50
   coverage in the middle so the crossover is continuous. Geometry lives in
   the inline <symbol id="mix-seam"> in index.html; this sizes and reveals it. */

.seam {
  display: block;
  width: 100%;
  /* The symbol is 1200x120 and scaled with `slice`, so it crops whichever axis
     is proportionally larger. Keeping the height near width/10 means narrow
     screens still see the whole indigo-to-amber ramp instead of a cropped
     mid-section, which would read as a muddy purple bar. */
  height: clamp(40px, 6.5vw, 92px);
  overflow: hidden;
  background: var(--indigo); /* shows through before the SVG paints */
  /* Revealed by a wipe on scroll; JS adds .is-in. Without JS or with reduced
     motion the clip-path below is overridden to fully open. */
  clip-path: inset(0 0 0 0);
}

.seam-svg {
  width: 100%;
  height: 100%;
}

/* The reveal wipe. Applied only when JS confirms motion is allowed. */
.js-motion .seam {
  clip-path: inset(0 100% 0 0);
  transition: clip-path 1.1s var(--ease);
}
.js-motion .seam.is-in { clip-path: inset(0 0 0 0); }

/* Alternating seams run the other way so the page doesn't read as four copies
   of one stripe. */
.seam--flip .seam-svg { transform: scaleX(-1); }

.seam-defs {
  position: absolute;
  width: 0; height: 0;
  overflow: hidden;
}

/* 6. Header ---------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: color-mix(in srgb, var(--ink) 88%, transparent);
  backdrop-filter: saturate(140%) blur(10px);
  -webkit-backdrop-filter: saturate(140%) blur(10px);
  border-bottom: 1px solid rgba(239, 234, 224, 0.1);
  --accent: var(--amber);
}
@supports not (backdrop-filter: blur(4px)) {
  .site-header { background: var(--ink); }
}

.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  min-height: 4.25rem;
}

.wordmark {
  font-family: var(--display);
  font-variation-settings: 'SOFT' 40, 'WONK' 1, 'opsz' 40;
  font-size: 1.2rem;
  letter-spacing: -0.015em;
  color: var(--bone);
  text-decoration: none;
  white-space: nowrap;
}
/* The one place the two halves of the palette touch in text. */
.wordmark .a { color: var(--bone); }
.wordmark .b { color: var(--amber); }

.site-nav ul {
  display: flex;
  align-items: center;
  gap: clamp(0.9rem, 2.6vw, 2.25rem);
  list-style: none;
}
.site-nav a {
  font-family: var(--mono);
  font-size: var(--t-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--mist);
  text-decoration: none;
  padding-block: 0.5rem;
  transition: color 0.25s var(--ease);
}
.site-nav a:hover { color: var(--bone); }

/* Below 34rem the nav would crowd the wordmark — drop the two least-critical
   items rather than introducing a hamburger menu for four links. */
@media (max-width: 33.999rem) {
  .site-nav li.is-optional { display: none; }
}

/* 7. Hero ------------------------------------------------------------------ */

.hero {
  --bg: var(--ink);
  --fg: var(--bone);
  --fg-mute: var(--mist);
  --accent: var(--amber);
  background: var(--ink);
  color: var(--bone);
  padding-block: clamp(4.5rem, 14vh, 9rem) clamp(3.5rem, 9vh, 6rem);
  min-height: min(88vh, 60rem);
  display: flex;
  align-items: center;
  position: relative;
  overflow: hidden;
}

/* A very quiet dot field behind the hero — same halftone vocabulary as the
   seam, at ~4% so it registers as texture, not decoration. */
.hero::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle at 1px 1px, rgba(239, 234, 224, 0.5) 1px, transparent 1.4px);
  background-size: 22px 22px;
  opacity: 0.055;
  pointer-events: none;
}

.hero .wrap { position: relative; }

.hero__eyebrow { margin-bottom: clamp(1.5rem, 4vw, 2.75rem); }

.hero h1 {
  font-size: var(--t-hero);
  font-variation-settings: 'SOFT' 40, 'WONK' 1, 'opsz' 144;
  letter-spacing: -0.035em;
  line-height: 0.94;
  max-width: 16ch;
}
.hero h1 .line-2 {
  display: block;
  color: var(--amber);
}

.hero__sub {
  margin-top: clamp(1.75rem, 4vw, 2.75rem);
  font-size: var(--t-md);
  line-height: 1.5;
  color: var(--mist);
  max-width: 50ch;
}
.hero__sub strong {
  color: var(--bone);
  font-weight: 500;
}

.hero__actions {
  margin-top: clamp(2.25rem, 5vw, 3.5rem);
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 2.25rem;
}

/* 8. Philosophy ------------------------------------------------------------ */

.beliefs { list-style: none; }

.beliefs li {
  display: grid;
  grid-template-columns: 3.25rem 1fr;
  align-items: baseline;
  gap: 0 0.5rem;
  padding-block: clamp(1rem, 2.4vw, 1.6rem);
  border-top: 1px solid var(--rule);
}
.beliefs li:last-child { border-bottom: 1px solid var(--rule); }

.beliefs .n {
  font-family: var(--mono);
  font-size: var(--t-mono);
  color: var(--accent);
  letter-spacing: 0.1em;
}

.beliefs .b {
  font-family: var(--display);
  font-variation-settings: 'SOFT' 30, 'WONK' 1, 'opsz' 72;
  font-size: var(--t-lg);
  line-height: 1.14;
  letter-spacing: -0.02em;
}

@media (max-width: 30rem) {
  .beliefs li { grid-template-columns: 2.25rem 1fr; }
}

/* 9. Services -------------------------------------------------------------- */

.services { list-style: none; }

.service {
  display: grid;
  grid-template-columns: 4rem minmax(0, 15rem) minmax(0, 1fr);
  gap: 0.5rem clamp(1.5rem, 3vw, 3rem);
  align-items: start;
  padding-block: clamp(1.75rem, 4vw, 2.75rem);
  border-top: 1px solid var(--rule);
}
.service:last-child { border-bottom: 1px solid var(--rule); }

.service .n {
  font-family: var(--mono);
  font-size: var(--t-mono);
  color: var(--accent);
  letter-spacing: 0.1em;
  padding-top: 0.55em;
}

.service h3 {
  /* Deliberately smaller than --t-lg: this label sits in a fixed-width column
     beside its description, so it needs to settle in two lines, not four. */
  font-size: clamp(1.3rem, 1.05rem + 1vw, 1.75rem);
  font-variation-settings: 'SOFT' 30, 'WONK' 1, 'opsz' 60;
  line-height: 1.1;
  text-wrap: balance;
}

.service p {
  color: var(--fg-mute);
  max-width: 46ch;
  padding-top: 0.3em;
}

/* Stack to a single column before the three-column grid gets cramped. */
@media (max-width: 56rem) {
  .service {
    grid-template-columns: 3rem 1fr;
    gap: 0.4rem 1rem;
  }
  .service p { grid-column: 2; }
}

/* 10. Portfolio ------------------------------------------------------------ */
/* Image-led product grid. The surrounding section stays bone so the page keeps
   its ink/bone alternation; the grid sits inside its own dark panel, which is
   the ground the approved product-grid direction calls for. Section theming is
   re-declared on the panel so everything inside picks the ink-side accent. */

.pgrid-panel {
  --bg: var(--ink);
  --fg: var(--bone);
  --fg-mute: var(--mist);
  --accent: var(--amber);
  --rule: rgba(239, 234, 224, 0.14);
  background: var(--ink);
  color: var(--fg);
  padding: clamp(1.15rem, 2.6vw, 1.9rem);
  border-radius: 3px;
}

/* --- Filter row ---
   Progressive enhancement: hidden until main.js confirms it can actually do
   the filtering. Without JS every card is simply shown, which is the correct
   fallback for a four-item grid. */
.filters {
  display: none;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: clamp(1.15rem, 2.6vw, 1.9rem);
}
.js-filters .filters { display: flex; }

.filters__label {
  font-family: var(--mono);
  font-size: var(--t-mono);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--fg-mute);
  margin-right: 0.6rem;
}

.filter {
  font-family: var(--mono);
  font-size: var(--t-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-mute);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 0.5rem 1.05rem;
  cursor: pointer;
  transition: color 0.25s var(--ease),
              border-color 0.25s var(--ease),
              background-color 0.25s var(--ease);
}
.filter:hover {
  color: var(--fg);
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
}
.filter[aria-pressed="true"] {
  color: var(--ink);
  background: var(--accent);
  border-color: var(--accent);
}

/* --- The grid --- */
.pgrid {
  display: grid;
  /* 15rem lands all four across at desktop — 17rem overflowed the panel by a
     few pixels and wrapped the fourth card onto a row of its own. Falls to
     2-across on tablets and 1 on phones. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
}

.pcard { background: var(--ink); display: flex; }
.pcard[hidden] { display: none !important; }

.pcard__link,
.pcard__inner {
  display: flex;
  flex-direction: column;
  width: 100%;
  color: inherit;
  text-decoration: none;
}
/* The grid gap is 1px, so the standard 3px outline offset would be clipped by
   the neighbouring cell. Inset it instead — same ring, still clearly visible. */
.pcard__link:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }

.pcard__media {
  position: relative;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  background: var(--ink-lift);
}
.pcard__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  transition: transform 0.6s var(--ease);
}
.pcard__link:hover .pcard__media img { transform: scale(1.03); }

/* Screenshot cards: the phone sits in its frame, top-aligned, and runs off the
   bottom edge of the square. Nothing is scaled non-uniformly and the status bar
   stays whole — the crop only ever removes screen from the bottom. */
.pcard__media--device {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: clamp(1.25rem, 3vw, 1.9rem) clamp(1.25rem, 3vw, 1.9rem) 0;
  background: radial-gradient(120% 90% at 50% 0%, #24242E 0%, var(--ink) 72%);
}
.pcard__media--device .device {
  width: 74%;
  flex: 0 0 auto;
  transition: transform 0.5s var(--ease);
}
.pcard__link:hover .pcard__media--device .device { transform: translateY(-4px); }

/* Cards for products with no photography yet, and the archived one. A quiet
   halftone panel and a serif monogram — deliberately empty, not a broken
   image and not a stock placeholder. */
.pcard__media--blank {
  display: grid;
  place-items: center;
  background: var(--ink-lift);
}
.pcard__media--blank::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle at 2px 2px, var(--accent) 1.6px, transparent 2.1px);
  background-size: 12px 12px;
  opacity: 0.13;
  pointer-events: none;
}
.pcard__monogram {
  position: relative;
  font-family: var(--display);
  font-variation-settings: 'SOFT' 40, 'WONK' 1, 'opsz' 144;
  font-size: clamp(2.4rem, 6vw, 3.4rem);
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--fg-mute);
  opacity: 0.42;
}

.pcard__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: clamp(1.1rem, 2.4vw, 1.5rem);
}

.pcard__kind {
  font-family: var(--mono);
  font-size: var(--t-mono);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
}

.pcard__name {
  font-family: var(--display);
  font-variation-settings: 'SOFT' 40, 'WONK' 1, 'opsz' 72;
  font-size: clamp(1.5rem, 1.1rem + 1.2vw, 2.05rem);
  line-height: 1;
  letter-spacing: -0.02em;
}

.pcard__desc {
  color: var(--fg-mute);
  font-size: 0.95rem;
  line-height: 1.5;
}

.pcard__foot { margin-top: auto; padding-top: 1.1rem; }

.pcard__cta {
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
  font-family: var(--mono);
  font-size: var(--t-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  border-bottom: 1px solid currentColor;
  padding-block: 0.25em;
  transition: gap 0.3s var(--ease);
}
.pcard__link:hover .pcard__cta { gap: 1.1em; }

.pcard__status {
  font-family: var(--mono);
  font-size: var(--t-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-mute);
}

/* Archived: still present, plainly demoted. Dimming is applied per-element
   rather than to the whole card so the "Archived" status stays legible. */
.pcard--archived .pcard__media,
.pcard--archived .pcard__kind,
.pcard--archived .pcard__name { opacity: 0.5; }
.pcard--archived .pcard__desc { opacity: 0.75; }

/* 10b. Product detail pages ------------------------------------------------ */

.crumb {
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
  font-family: var(--mono);
  font-size: var(--t-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-mute);
  text-decoration: none;
  transition: color 0.25s var(--ease);
}
.crumb:hover { color: var(--accent); }

.product-hero { padding-block: clamp(2.75rem, 7vw, 5rem) var(--section); }

.product-hero__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 21rem), 1fr));
  gap: clamp(2.25rem, 5vw, 4.5rem);
  align-items: center;
  margin-top: clamp(1.75rem, 4vw, 2.5rem);
}

.product-hero h1 {
  font-size: var(--t-xl);
  font-variation-settings: 'SOFT' 40, 'WONK' 1, 'opsz' 144;
  letter-spacing: -0.03em;
  margin-top: 0.6rem;
}

.product-hero .lede { margin-top: clamp(1.25rem, 3vw, 1.75rem); }

.product-facts {
  list-style: none;
  margin-top: clamp(1.75rem, 4vw, 2.5rem);
  max-width: 30rem;
}
.product-facts li {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.5rem;
  justify-content: space-between;
  padding-block: 0.7rem;
  border-top: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: var(--t-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.product-facts li:last-child { border-bottom: 1px solid var(--rule); }
.product-facts .k { color: var(--fg-mute); }
.product-facts .v { color: var(--fg); }

.product-hero__actions {
  margin-top: clamp(1.75rem, 4vw, 2.5rem);
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 2.25rem;
}

/* A photograph presented as a photograph: full frame, correct aspect ratio,
   one hairline rule so it sits on the ink ground instead of floating. */
.shot {
  display: block; /* also used on <span> inside <figure>, which is inline */
  margin: 0;
  border: 1px solid var(--rule);
  border-radius: 3px;
  overflow: hidden;
  background: var(--ink-lift);
}
.shot img { width: 100%; height: auto; }

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
  gap: clamp(1.15rem, 2.6vw, 1.9rem);
}
.gallery figure { margin: 0; }
.gallery figcaption,
.shot figcaption {
  margin-top: 0.85rem;
  font-family: var(--mono);
  font-size: var(--t-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-mute);
}
.gallery .shot { margin-bottom: 0; }

/* --- Rotator (rotating gallery) ---
   Shared between the maknesan.html and koope.html heroes. Progressive
   enhancement, on the same pattern as .filters: with JS blocked the stage is
   simply the first photograph and the controls are never shown, so nobody is
   offered a button that cannot work.

   No layout shift: the stage reserves its space with `aspect-ratio` up front
   (--rot-ratio, set per instance) and every slide is absolutely positioned
   inside it, so nothing reflows as images swap or load. */

.rotator { display: block; }

/* KŌOPE's hero is a phone screenshot in a device frame; cap the whole
   component at a believable phone width so the controls line up under it. */
.rotator--narrow {
  width: min(20rem, 100%);
  margin-inline: auto;
}

.rotator__stage {
  position: relative;
  aspect-ratio: var(--rot-ratio, 1 / 1);
  overflow: hidden;
  border: 1px solid var(--rule);
  border-radius: 3px;
  background: var(--ink-lift);
}
/* Inside a .device__screen the frame already provides the edge. */
.rotator__stage--bare { border: 0; border-radius: 0; background: transparent; }

.rotator__slide {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
}
/* No-JS / pre-JS state: the first photograph, shown plainly. */
.rotator__slide:first-child { opacity: 1; }

/* Scoped through .rotator__stage so it outranks `.device__screen img`, which
   sets height:auto and is declared later in this file. */
.rotator__stage .rotator__slide img {
  width: 100%;
  height: 100%;
  /* `contain` rather than `cover`: every slide keeps its true proportions even
     if a future image doesn't match the stage ratio exactly — it letterboxes
     against --ink-lift instead of being cropped or squashed. */
  object-fit: contain;
  object-position: center;
}

/* Once main.js takes over it drives visibility itself. Declared after the
   :first-child rule above so it wins the equal-specificity tie. */
.js-rotator .rotator__slide { opacity: 0; }
.js-rotator .rotator__slide.is-current { opacity: 1; }

/* The crossfade is opt-in on .js-motion, the same gate the reveals use, so a
   visitor who prefers reduced motion gets an instant swap on manual
   navigation and never a fade. Both classes sit on the <html> element, so this
   is a compound selector, not a descendant one. */
.js-motion.js-rotator .rotator__slide { transition: opacity 0.9s var(--ease); }

/* --- Rotator controls --- */

.rotator__controls {
  display: none;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  margin-top: 1rem;
}
.js-rotator .rotator__controls { display: flex; }

.rotator__arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  flex: 0 0 auto;
  border: 1px solid var(--rule);
  border-radius: 999px;
  color: var(--fg-mute);
  font-family: var(--mono);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  transition: color 0.25s var(--ease),
              border-color 0.25s var(--ease);
}
.rotator__arrow:hover {
  color: var(--fg);
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
}

.rotator__dots {
  display: flex;
  align-items: center;
  gap: 0.15rem;
  list-style: none;
  margin-inline: 0.35rem;
}

/* The visible dot is small, so the button around it is padded out to a
   comfortable 2.25rem tap target and the focus ring lands on that. */
.rotator__dot {
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  cursor: pointer;
}
.rotator__dot::before {
  content: '';
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 999px;
  border: 1px solid var(--fg-mute);
  background: transparent;
  transition: background-color 0.25s var(--ease),
              border-color 0.25s var(--ease),
              transform 0.25s var(--ease);
}
.rotator__dot:hover::before { border-color: var(--accent); }
.rotator__dot[aria-current="true"]::before {
  background: var(--accent);
  border-color: var(--accent);
  transform: scale(1.15);
}

/* --- Device frame ---
   The KŌOPE assets are real phone screenshots (1284x2778, iOS status bar and
   home indicator included). Rather than hide that, they are shown whole inside
   a plain rounded frame so they read as an intentional in-app screen. `height:
   auto` on the image means the 1284:2778 ratio is never distorted. */
.device {
  position: relative;
  padding: 5px;
  border: 1px solid rgba(239, 234, 224, 0.24);
  border-radius: 1.75rem;
  background: #0A0A0F;
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.45);
}
.device__screen {
  display: block;
  border-radius: 1.4rem;
  overflow: hidden;
}
.device__screen img { width: 100%; height: auto; display: block; }

/* Whole-phone presentation for the detail pages. Capped in rem so the screen
   never grows past a believable phone size on a wide monitor. */
.device--full {
  width: min(20rem, 100%);
  margin-inline: auto;
}

.device-row {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(1.5rem, 4vw, 3rem);
  justify-content: center;
  align-items: flex-start;
}
.device-row figure { margin: 0; flex: 0 1 20rem; }
.device-row figcaption {
  margin-top: 1rem;
  text-align: center;
  font-family: var(--mono);
  font-size: var(--t-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-mute);
}

.prose {
  max-width: var(--measure);
  display: grid;
  gap: 1.1rem;
}
.prose p { color: var(--fg-mute); }

/* 11. Contact -------------------------------------------------------------- */

.contact-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
  gap: clamp(2.5rem, 6vw, 5rem);
  align-items: start;
}

.contact__note {
  font-family: var(--mono);
  font-size: var(--t-mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-mute);
  margin-top: 2rem;
  line-height: 1.9;
}

.form { display: grid; gap: 1.4rem; }

.field { display: grid; gap: 0.5rem; }

.field label {
  font-family: var(--mono);
  font-size: var(--t-mono);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-mute);
}

.field input,
.field textarea {
  width: 100%;
  font-family: var(--body);
  font-size: var(--t-body);
  color: var(--fg);
  background: var(--ink-lift);
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 0.85rem 1rem;
  transition: border-color 0.25s var(--ease), background-color 0.25s var(--ease);
}
.field textarea { resize: vertical; min-height: 8rem; }
.field input::placeholder,
.field textarea::placeholder { color: var(--fg-mute); opacity: 0.6; }
.field input:hover,
.field textarea:hover { border-color: color-mix(in srgb, var(--accent) 45%, transparent); }

.btn {
  justify-self: start;
  display: inline-flex;
  align-items: center;
  gap: 0.7em;
  font-family: var(--mono);
  font-size: var(--t-mono);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
  background: var(--accent);
  padding: 0.95rem 1.6rem;
  border-radius: 2px;
  cursor: pointer;
  text-decoration: none;
  transition: transform 0.25s var(--ease), filter 0.25s var(--ease);
}
.btn:hover { filter: brightness(1.08); transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

.form__hint {
  font-family: var(--mono);
  font-size: var(--t-mono);
  color: var(--fg-mute);
  letter-spacing: 0.06em;
  line-height: 1.7;
}

/* 12. Footer --------------------------------------------------------------- */

.site-footer {
  background: var(--ink);
  color: var(--mist);
  --accent: var(--amber);
  padding-block: clamp(3rem, 7vw, 4.5rem);
  border-top: 1px solid rgba(239, 234, 224, 0.1);
}

.footer-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 2rem 3rem;
  justify-content: space-between;
  align-items: flex-end;
}

.site-footer .wordmark { font-size: 1.35rem; }

.footer-meta {
  font-family: var(--mono);
  font-size: var(--t-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  line-height: 2;
  list-style: none;
}
.footer-meta a { text-decoration: none; color: var(--mist); transition: color 0.25s var(--ease); }
.footer-meta a:hover { color: var(--amber); }

.colophon {
  margin-top: clamp(2rem, 5vw, 3rem);
  padding-top: 1.5rem;
  border-top: 1px solid rgba(239, 234, 224, 0.1);
  font-family: var(--mono);
  font-size: var(--t-mono);
  letter-spacing: 0.1em;
  color: var(--graphite);
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 2rem;
  justify-content: space-between;
}

/* 13. Scroll reveal -------------------------------------------------------- */
/* Opt-in: only applies once JS has added .js-motion, which it does only when
   prefers-reduced-motion is not set. No JS or reduced motion => no transform,
   content is visible at its natural position. */

.js-motion .reveal {
  opacity: 0;
  transform: translateY(1.4rem);
  transition: opacity 0.8s var(--ease), transform 0.8s var(--ease);
}
.js-motion .reveal.is-in {
  opacity: 1;
  transform: none;
}

/* 14. 404 ------------------------------------------------------------------ */

.notfound {
  min-height: 70vh;
  display: flex;
  align-items: center;
}
.notfound h1 {
  font-size: var(--t-hero);
  font-variation-settings: 'SOFT' 40, 'WONK' 1, 'opsz' 144;
  line-height: 0.94;
}
.notfound p { color: var(--mist); margin-top: 1.5rem; max-width: 40ch; }
.notfound .link { margin-top: 2.5rem; }

/* 15. Print ---------------------------------------------------------------- */

@media print {
  .site-header, .seam, .hero::before, .filters,
  .rotator__controls,
  .pcard__media--blank::before { display: none !important; }
  .pgrid-panel { background: #fff; color: #000; padding: 0; }
  .pcard { background: #fff; }
  body { background: #fff; color: #000; }
  .section { background: #fff; color: #000; padding-block: 1.5rem; }
}
