/* ohms.studio design system
   Every pattern here is measured from the awwwards deep set, with the source
   site named. Nothing invented. Query the corpus: py corpus.py q "<terms>"

   Convergence counts across 74 independent per-site notes:
     fluid type root, no breakpoints ... 9 notes
     mono label system ................ 11 notes
     line-mask reveal ................. 10 notes
     one hover primitive ............... 8 notes
     section inversion ................. 7 notes
*/

/* ---------------------------------------------------------------------------
   TYPE ROOT  [tracing-art]
   Every size is authored in rem against a viewport-derived root, so the whole
   page scales with no per-breakpoint headline rules. tracing-art is the only
   one of the nine that handles the bottom end: a bare vw root breaks below
   450px and ignores the browser's own font-size preference. Both ends clamp.
--------------------------------------------------------------------------- */
html { font-size: 0.625vw; }                                   /* 10px @ 1600 */
@media (min-width: 1920px) { html { font-size: 12px; } }
@media (max-width: 900px)  { html { font-size: 12px; } }
@media (max-width: 450px)  { html { font-size: 2.66667vw; } }

:root {
  --paper:     #f3f4f1;
  --ink:       #131a14;
  --ink-70:    #4a5449;
  --ink-45:    #7c847a;
  --sage:      #769b78;
  --sage-text: #5a7c5c;
  --ink-sage:  #1a2d1b;

  /* Role tokens. Section inversion swaps these, nothing else.  [son-daven] */
  --ground: var(--paper);
  --figure: var(--ink);
  --muted:  var(--ink-45);
  --rule:   rgba(19, 26, 20, .14);
  --accent: var(--sage-text);

  /* One opacity token carries the whole label layer.  [the-other-side-of-truth] */
  --label-alpha: .55;

  --gut: 5.5vw;                                          /* [produx-design] */
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* Two eases run every interaction on the site.  [c2-montreal] */
  --ease-out: cubic-bezier(1, 0, 0.25, 0.995);
  --ease-in:  cubic-bezier(0.19, 1, 0.22, 1);
}

[data-ground="dark"] {
  --ground: var(--ink-sage);
  --figure: var(--paper);
  --muted:  #93a894;
  --rule:   rgba(243, 244, 241, .16);
  --accent: var(--sage);
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--figure);
  font-family: Archivo, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 1.6rem;
  line-height: 1.45;
  overflow-x: hidden;
  /* Ground crossfades rather than snapping.  [b-d-r-jams] */
  transition: background-color .45s ease, color .45s ease;
}
a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; }

/* ---------------------------------------------------------------------------
   THE LABEL LAYER  [11 notes converge]
   ~11px uppercase mono at a single opacity token, carrying every caption,
   index, timestamp and footer row. hobro-digital is the closest register
   match; kpr supplies the square bullet; fluid-glass the hairline above.
--------------------------------------------------------------------------- */
.label {
  font-family: var(--mono);
  font-size: 1.1rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--figure);
  opacity: var(--label-alpha);
}
.label--rule {
  display: flex; align-items: center; gap: 1.2rem;
  padding-bottom: 1.4rem;
}
.label--rule::after {
  content: ""; flex: 1; height: 1px; background: currentColor; opacity: .35;
}
/* 4.5px square bullet in currentColor.  [kpr] */
.label--dot::before {
  content: ""; display: inline-block; width: 4.5px; height: 4.5px;
  background: currentColor; margin-right: .9rem; vertical-align: .12em;
}
/* .0001 numbering turns a list into an index.  [the-other-side-of-truth] */
.idx { font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------------------
   THE HOVER PRIMITIVE  [c2-montreal, 8 notes converge]
   One 1px rule scaling 0 to 1 with the transform-origin flipped between enter
   and leave, so it exits the way it came instead of reversing. c2-montreal:
   "That single pattern plus two eases is what makes a near-static page read as
   considered rather than unfinished." exo-ape supplies the origin flip.
--------------------------------------------------------------------------- */
.u { position: relative; display: inline-block; }
.u::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -.3rem;
  height: 1px; background: currentColor;
  transform: scaleX(0); transform-origin: right center;
  transition: transform .4s var(--ease-out);
}
.u:hover::after { transform: scaleX(1); transform-origin: left center;
                  transition: transform .6s var(--ease-in); }

/* Variable-font hover: no underline, no colour change, weight steps.  [inkfish] */
.wf { transition: font-variation-settings .3s cubic-bezier(.25,1,.5,1); }
.wf:hover { font-variation-settings: "wght" 640; }

/* ---------------------------------------------------------------------------
   LINE-MASK REVEAL  [david-whyte, 10 notes converge]
   Wrap each line, move only the inner element, fire once. david-whyte's is the
   one that matters for a no-library build: an observer toggles a class and sets
   transitionDelay per line, handing the whole animation to CSS transitions so
   NO per-frame JavaScript runs at all.
--------------------------------------------------------------------------- */
.rv { display: block; overflow: hidden; }
.rv > * {
  display: block;
  transform: translate3d(0, 102%, 0);            /* 102% clears descenders */
  transition: transform 1.3s cubic-bezier(.19, 1, .22, 1);
}
.rv.in > * { transform: translate3d(0, 0, 0); }

/* Container fade-up, the quieter sibling.  [opal-tadpole, era-residence] */
.fu { opacity: 0; transform: translateY(3.3rem);
      transition: opacity .8s ease, transform .8s cubic-bezier(.19,1,.22,1); }
.fu.in { opacity: 1; transform: none; }

/* ---------------------------------------------------------------------------
   IMAGES  [synchrodogs]
   padding-top carries the real aspect ratio so nothing shifts on load, and an
   unloaded slot reads as a deliberate ink block rather than a gap.
--------------------------------------------------------------------------- */
/* display:block is load-bearing. These are spans, and an inline box ignores
   both width and the padding-top that carries the ratio, so the frame never
   opens and the absolutely-positioned image has nothing to fill. */
.asset { display: block; position: relative; overflow: hidden; background: var(--ink-sage); }
.asset::before { content: ""; display: block; padding-top: var(--ratio, 62.5%); }
.asset img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover;
}

/* ---------------------------------------------------------------------------
   MASTHEAD
   Fixed, and it stays legible over paper and ink-sage bands because the ground
   tokens it reads are rewritten by anchor elements.  [sculpting-harmony]
--------------------------------------------------------------------------- */
.mast {
  position: fixed; inset: 0 0 auto; z-index: 20;
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 2.4rem; padding: 2.6rem var(--gut);
  color: var(--figure);
  transition: color .45s ease;
}
/* The scrim ships with the colour swap and is not optional. sculpting-harmony
   pairs its token rewrite with linear-gradient(180deg, ground 35%, transparent).
   Without it the masthead is legible over a band and illegible the moment an
   image scrolls under it. */
.mast::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: linear-gradient(180deg, var(--ground) 35%, transparent);
  transition: background .45s ease;
}
.mast__mark { font-size: 1.7rem; font-weight: 600; letter-spacing: -.015em; }
.mast__mark .dot { color: var(--accent); }
.mast__nav { display: flex; gap: 2.2rem; }
@media (max-width: 720px) { .mast__nav a:not(.is-primary) { display: none; } }

/* One live detail: bracketed status label.  [steven-com] */
.status { font-family: var(--mono); font-size: 1.1rem; letter-spacing: .08em;
          color: var(--figure); opacity: var(--label-alpha); }

/* ---------------------------------------------------------------------------
   LAYOUT
   lusion-v3: headline confined to a column range so the left third stays empty
   and the page reads composed rather than loud. immersive-garden: the spacing
   ratio is what stops restraint reading as sparse.
--------------------------------------------------------------------------- */
.wrap { padding-left: var(--gut); padding-right: var(--gut); }

/* The 12-column grid. produx-design: 12 columns at 5.5vw gutters. Everything
   structural sits on it, which is the difference between measured empty space
   and space that is merely empty. */
.grid12 {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 0 1.6rem;
  padding-left: var(--gut); padding-right: var(--gut);
}

/* lusion-v3: the headline is confined to columns 4 through 8 so the left third
   stays empty and the page reads composed rather than loud. A max-width does
   not do this; it centres or left-aligns, and the emptiness lands wherever it
   happens to. Confinement puts it somewhere on purpose.
   immersive-garden: 11.11vw above and 4.17vw below is the ratio the note calls
   "what makes the page feel composed rather than sparse." */
.lede { padding-top: 11.11vw; padding-bottom: 4.17vw; }
.lede__head { grid-column: 4 / 9; }
.lede__eyebrow { grid-column: 1 / 4; padding-top: .5rem; }
.lede h1 {
  margin: 0; font-weight: 500; letter-spacing: -.028em; line-height: 1.06;
  font-size: 4.2rem;
}
.lede p {
  margin: 2.4rem 0 0; max-width: 52ch; color: var(--muted);
  font-size: 1.7rem; line-height: 1.55;
}
@media (max-width: 860px) {
  .lede__head, .lede__eyebrow { grid-column: 1 / -1; }
  .lede__eyebrow { padding-bottom: 2.4rem; }
  .lede { padding-top: 14rem; }
  /* The reveal needs one line per clipped wrapper. At 390px the desktop size
     wraps two of the five, which slides two lines as one block and breaks the
     pattern. Size to the measure, not the other way round. */
  .lede h1 { font-size: 2.9rem; letter-spacing: -.022em; }
}

.band { padding: 6rem 0; background: var(--ground); color: var(--figure);
        transition: background-color .45s ease, color .45s ease; }

footer {
  padding: 2.6rem var(--gut) 3.4rem; border-top: 1px solid var(--rule);
  display: flex; flex-wrap: wrap; gap: .9rem 2.8rem;
}
footer .sp { margin-left: auto; }

/* ---------------------------------------------------------------------------
   SPEC-SHEET ANNOTATION  [sharplink, via library section 5]
   A faint dotted measurement grid with a numbered callout on a dashed leader.
   It reframes a plain screenshot as an engineered, measured thing, which is
   the right register for a studio that builds back ends.

   ONE callout per image, not four. The source site runs four; our own standing
   rule is a ceiling of one annotation label, and the build plan flags the
   conflict explicitly. The grid carries the "measured" reading, so the label
   does not have to.
--------------------------------------------------------------------------- */
.annot { position: relative; }
.annot::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 2;
  background-image:
    repeating-linear-gradient(to right, rgba(243,244,241,.22) 0 1px, transparent 1px 40px),
    repeating-linear-gradient(to bottom, rgba(243,244,241,.22) 0 1px, transparent 1px 40px);
  opacity: 0; transition: opacity .9s var(--ease-in);
}
.annot.in::after { opacity: 1; }
.annot__call {
  position: absolute; z-index: 3; display: flex; align-items: center; gap: .8rem;
  font-family: var(--mono); font-size: 1.1rem; letter-spacing: .08em;
  text-transform: uppercase; color: var(--paper);
  opacity: 0; transform: translateY(.6rem);
  transition: opacity .6s ease .5s, transform .6s var(--ease-in) .5s;
}
.annot.in .annot__call { opacity: 1; transform: none; }
.annot__call::before {
  content: attr(data-n); display: grid; place-items: center;
  width: 2.2rem; height: 2.2rem; border: 1px solid currentColor; border-radius: 50%;
  font-size: 1rem;
}
.annot__call::after {
  content: ""; position: absolute; left: 2.2rem; top: 50%; width: var(--leader, 9rem);
  border-top: 1px dashed currentColor; opacity: .55;
}
.annot__call > span { margin-left: calc(var(--leader, 9rem) + .8rem); white-space: nowrap; }
@media (max-width: 720px) { .annot__call { display: none; } .annot::after { opacity: 0 !important; } }

@media (prefers-reduced-motion: reduce) {
  .rv > *, .fu { transition: none !important; transform: none !important; opacity: 1 !important; }
  body, .band, .mast { transition: none !important; }
}
