/* maioka.co — base layer: reset, paper, grain, type defaults */

@import url("tokens.css");
@import url("fonts.css");

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  /* Reference 1's custom arrow. Declared once here and once on the interactive
     state below; both data URIs live in tokens.css. */
  cursor: var(--cursor);
  font-family: var(--font);
  font-size: var(--t-body);
  font-weight: 400;
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, p, figure, blockquote { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
img, video { display: block; max-width: 100%; height: auto; }

/* Anything clickable takes the inverted arrow, so the cursor answers "yes,
   this" with a change of value rather than a change of shape. */
a, button, summary, [role="button"] { cursor: var(--cursor-pick); }

a {
  color: inherit;
  text-decoration: none;
  border-bottom: var(--hairline) solid var(--rule);
  transition: border-color var(--d-fast) var(--e-out);
}
a:hover { border-color: var(--ink); }

button {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  padding: 0;
}

/* Focus must be clearly visible against paper. Never remove it. */
:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

/* ---- type roles ------------------------------------------------------ */

.t-mega {
  font-size: var(--t-mega);
  font-weight: 500;
  line-height: var(--lh-mega);
  letter-spacing: var(--tr-mega);
}
.t-lead {
  font-size: var(--t-lead);
  font-weight: 400;
  line-height: var(--lh-lead);
}
/* UI labels are uppercase; body copy stays lowercase — that tension is
   deliberate and carries Ken's existing voice. */
.t-label {
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
}
.t-micro {
  font-size: var(--t-micro);
  font-weight: 400;
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* Vertical type — texture and signature only, never wayfinding.
   Reserved for section labels, client names and years. */
.t-vertical {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  white-space: nowrap;
}

.rule { border: 0; border-top: var(--hairline) solid var(--rule); margin: 0; }

/* ---- grain -----------------------------------------------------------
   One fixed viewport-sized noise layer over everything. Generated, not
   shipped as an image, so it costs nothing to download. It must never
   intercept pointer events and must not scroll with content. */
.grain {
  position: fixed;
  /* One tile of slack on every side. grain-drift moves the whole layer by up
     to one tile, and background-repeat tiles inside the element's own box, so
     an inset:0 layer uncovers the strip it vacates — up to 133px of ungrained
     paper down the right and bottom edges, pulsing in six steps every eight
     seconds. The slack is clipped by the viewport and costs nothing. */
  inset: calc(var(--grain-tile) * -1);
  z-index: 9999;
  pointer-events: none;
  opacity: var(--grain-opacity);
  mix-blend-mode: var(--grain-blend);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: var(--grain-tile) var(--grain-tile);
}
@media (prefers-reduced-motion: no-preference) {
  /* imperceptible drift keeps the grain from looking like a dead texture */
  .grain { animation: grain-drift var(--grain-drift) steps(var(--grain-steps)) infinite; }
}
@keyframes grain-drift {
  0%   { transform: translate(0, 0); }
  100% { transform: translate(calc(var(--grain-tile) * -1), calc(var(--grain-tile) * -1)); }
}

/* ---- layout helpers -------------------------------------------------- */
.page { padding-inline: var(--margin); }

.screenreader {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Progressive enhancement: the ring only exists when JS says so. Without
   JS the gallery is the site, and it must look intentional, not broken.

   Keyed on the NEGATIVE — `html:not(.has-js)` — on purpose. The earlier form
   was `.js-only { display:none }` plus `html.has-js .js-only { display:revert }`,
   and that `revert` (0,2,1) outranked every component's own `display` and every
   media query that tried to change it: `.works-modes { display:flex }` was dead
   and read as `block`, and `@media (max-width:640px) { .works-hint {display:none} }`
   could not hide the hint. Hiding only when JS is absent leaves the component's
   own display rules untouched in the case that actually renders. */
html:not(.has-js) .js-only { display: none; }
html.has-js .no-js-only { display: none; }
