/* ==========================================================================
   eriscolabs — black and white only. Weight, scale and rhythm do colour's work.
   The only graphic element besides type is a 1px hairline.
   ========================================================================== */

@font-face {
  font-family: 'Schibsted Grotesk';
  font-weight: 400 900;
  font-style: normal;
  font-display: swap;
  src: url('../fonts/schibsted-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Schibsted Grotesk';
  font-weight: 400 900;
  font-style: normal;
  font-display: swap;
  src: url('../fonts/schibsted-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* --- tokens --------------------------------------------------------------- */

:root {
  --paper: #fff;
  --ink: #000;
  --hair: rgba(0, 0, 0, .15);
  --hair-s: rgba(0, 0, 0, .28);
  --mute: rgba(0, 0, 0, .58);
  --wash: rgba(0, 0, 0, .04);

  --gut: clamp(20px, 5vw, 72px);
  --max: 1560px;
  --sec: clamp(96px, 13vh, 168px);

  --ease: cubic-bezier(.16, 1, .3, 1);
  --ease-io: cubic-bezier(.65, 0, .35, 1);
  --t: .9s;

  color-scheme: light;
}

:root[data-theme='dark'] {
  --paper: #000;
  --ink: #fff;
  --hair: rgba(255, 255, 255, .19);
  --hair-s: rgba(255, 255, 255, .34);
  --mute: rgba(255, 255, 255, .62);
  --wash: rgba(255, 255, 255, .06);
  color-scheme: dark;
}

/* --- base ----------------------------------------------------------------- */

*, *::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);
  font-family: 'Schibsted Grotesk', system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: clamp(15px, .35vw + 14px, 17px);
  line-height: 1.55;
  font-weight: 400;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

h1, h2, h3, p, ul, figure { margin: 0; }
ul { list-style: none; padding: 0; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
svg { display: block; }
::selection { background: var(--ink); color: var(--paper); }

:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
  border-radius: 2px;
}

.skip {
  position: fixed;
  top: 10px; left: 10px;
  z-index: 100;
  padding: 10px 16px;
  background: var(--ink);
  color: var(--paper);
  font-size: .85rem;
  transform: translateY(-200%);
}
.skip:focus { transform: none; }

/* --- scroll progress: a single hairline, the page's own measure ----------- */

.prog {
  position: fixed;
  inset: 0 0 auto 0;
  height: 1px;
  z-index: 60;
  background: var(--ink);
  transform-origin: 0 50%;
  transform: scaleX(0);
  pointer-events: none;
}
@supports (animation-timeline: scroll()) {
  .anim .prog {
    animation: prog linear;
    animation-timeline: scroll(root block);
  }
}
@keyframes prog { to { transform: scaleX(1); } }

/* --- header --------------------------------------------------------------- */

.hd {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 18px var(--gut);
  /* solid paper, not glass: hairlines are the only graphic element this world
     allows, and a saturate() filter is inert on a monochrome page anyway */
  background: var(--paper);
  border-bottom: 1px solid transparent;
  transition: border-color .4s var(--ease);
}
.hd.stuck { border-bottom-color: var(--hair); }

.hd-mark { display: block; padding: 4px; margin: -4px; }

.mark {
  display: block;
  width: 29px;
  aspect-ratio: 1000 / 1367;
  background: currentColor;
  -webkit-mask: url('../img/eriscolabs.svg') no-repeat center / contain;
  mask: url('../img/eriscolabs.svg') no-repeat center / contain;
  transition: opacity .3s var(--ease);
}
.hd-mark:hover .mark { opacity: .55; }

.hd-nav {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2vw, 30px);
}

.nav-l {
  position: relative;
  font-size: .875rem;
  font-weight: 500;
  letter-spacing: -.005em;
  white-space: nowrap;
  transition: opacity .25s var(--ease);
}
a.nav-l::after,
.tog::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: -3px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: 100% 50%;
  transition: transform .45s var(--ease);
}
a.nav-l:hover::after,
.tog:hover::after { transform: scaleX(1); transform-origin: 0 50%; }

.tog { display: flex; gap: 5px; font-variant-numeric: tabular-nums; }
/* The faded half of the switch is the name of the language you would land in,
   so it is text to be read, not a disabled control. At .3/.38 it measured
   2.11–3.39:1 across both themes — under AA in all four combinations. .55 is
   the first step that clears it everywhere (4.76:1 on paper, 6.27:1 in the
   dark) and it is still plainly the quieter half of the pair. Measured, not
   guessed: tests/contrast.mjs sweeps both pages in both themes. */
.tog-s { opacity: .55; }
.tog-a, .tog-b { transition: opacity .3s var(--ease), font-weight .3s var(--ease); }
.tog-b { opacity: .55; }
:root[lang='en'] .tog-a { opacity: .55; }
:root[lang='en'] .tog-b { opacity: 1; }

.ico { display: grid; place-items: center; width: 26px; height: 26px; }
.ico svg { width: 19px; height: 19px; transition: transform .6s var(--ease); }
.ico:hover svg { transform: rotate(180deg); }

.cta {
  padding: 9px 18px;
  border: 1px solid var(--hair-s);
  border-radius: 100px;
  transition: background .35s var(--ease), color .35s var(--ease), border-color .35s var(--ease);
}
a.cta::after { display: none; }
.cta:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }

/* --- layout --------------------------------------------------------------- */

main { display: block; }

.hero,
.sec {
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--gut);
}

.sec { padding-block: var(--sec); border-top: 1px solid var(--hair); }
.sec:first-of-type { border-top: 0; }

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

.display {
  font-size: clamp(3.4rem, 12.5vw, 11rem);
  /* .92 is as tight as Turkish diacritics allow: below it, the y of Arayüz
     runs into Yazılım and the ü dots touch the line above. */
  line-height: .92;
  letter-spacing: -.05em;
  font-weight: 600;
  text-wrap: balance;
}
.display.sm { font-size: clamp(2.6rem, 8vw, 6.5rem); }
/* flex, so the reveal boxes' negative margins cannot collapse into each other
   and loosen the leading */
.stack { display: flex; flex-direction: column; }

.h2 {
  font-size: clamp(1.5rem, 2.6vw, 2.1rem);
  line-height: 1.05;
  letter-spacing: -.03em;
  font-weight: 500;
}

.lead {
  font-size: clamp(1.05rem, 1.15vw + .75rem, 1.45rem);
  line-height: 1.42;
  letter-spacing: -.017em;
  font-weight: 400;
  max-width: 30ch;
  color: var(--mute);
}

/* The slogan is a statement, not a description: full ink, a step up the ramp
   and a touch more weight, so it holds its own under the display stack. */
.slogan {
  color: var(--ink);
  font-size: clamp(1.15rem, 1.5vw, 1.9rem);
  font-weight: 500;
  letter-spacing: -.024em;
  max-width: 24ch;
}

/* --- hero ----------------------------------------------------------------- */

.hero {
  position: relative;
  /* own stacking context, so the field's negative z-index stays inside the
     hero instead of falling behind the page background */
  isolation: isolate;
  padding-block: clamp(40px, 6vh, 88px) clamp(72px, 12vh, 150px);
  min-height: min(92svh, 980px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: clamp(28px, 4vh, 56px);
}

/* the undulating point marquee — full-bleed across the hero, never in the way */
.field {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 100vw;
  /* a canvas is a replaced element: with height:auto it takes its size from its
     own width/height attributes and quietly ignores `bottom`, leaving a bare
     band at the foot of the hero. State the height and the aspect ratio loses. */
  height: 100%;
  transform: translateX(-50%);
  z-index: -1;
  pointer-events: none;
  display: block;
}

/* The doors are promised in the first viewport, so the display is capped by
   viewport height as well as width — on a short laptop screen the type gives
   way, never the call to action. */
.hero .display { font-size: min(clamp(3.4rem, 12.5vw, 11rem), 20vh); }

/* --- the pinned exit ------------------------------------------------------ */

/* The hero is held at the top of the screen for one extra screen of scrolling
   while the headline grows past the reader, and the page below rides up over
   it. The whole mechanism is `position: sticky` plus a negative margin — no
   scroll library, no scroll hijacking, and the page still scrolls natively.

   The arithmetic, and it has to hold or the two halves drift apart:
     .pin is 200svh and the hero is 100svh, so the hero stays pinned across the
     first 100svh of scrolling. `.after` is pulled up by exactly 100svh, which
     puts its top edge at the fold at rest and lands it flush against the top of
     the screen at precisely the scroll position where the hero lets go.

   All of it is gated behind `.anim`: without it the hero is an ordinary block
   in the flow, which is what reduced-motion and no-JS visitors get. */
/* The headline grows well past the screen on the way out: it may spill, it may
   not widen the document. The clip belongs here and nowhere else — on the root
   it propagates to the viewport and kills the sticky pin outright; on the body
   it does not clip at all; and on the hero it would cut the full-bleed field
   down to the content measure, leaving a bare band down each edge on any screen
   wider than the container. `.pin` is full-bleed, so it trims the overspill
   without touching the field. */
.anim .pin { position: relative; height: 200svh; overflow-x: clip; }

.anim .pin .hero {
  position: sticky;
  top: 0;
  height: 100svh;
  min-height: 0;
  /* Deliberately NOT clipped. The hero's box is capped by the container, so on
     any screen wider than that a clip here would cut the full-bleed field down
     to the content measure and leave a bare band down each edge. The headline
     is free to grow past the box instead; what it must not do is widen the
     document, and that is handled once, on the root. */
}

/* transform and opacity only — the two things a compositor can animate without
   touching layout or paint */
.anim .pin .display,
.anim .pin .hero-b {
  will-change: transform, opacity;
  transform-origin: 18% 44%;
}
.anim .pin .hero-b { transform-origin: 0 50%; }

/* Everything after the pin is one panel, and it rides over the hero as a single
   sheet. It has to be its own element rather than a rule on each section for a
   reason the sections cannot satisfy: they are capped at the container measure,
   so paper laid on them stops short of the screen on any wider display and the
   field shows through in a band down each edge. `.after` is full-bleed, so its
   paper reaches the corners.

   It also has to be a layer. A sticky hero is a *positioned* element, and
   positioned elements paint over static ones wherever their boxes overlap,
   whatever the document order says.

   It stays square: a rounded panel would be the first radius on a content block
   in this system. */
.anim .after,
.anim .ft {
  position: relative;
  z-index: 2;
  background: var(--paper);
}

.anim .after { margin-top: -100svh; }

/* A phone gives up less of its screen to the show. The pull-up has to stay at or
   under `pin − 100svh` or the panel would already be showing above the fold at
   rest, so shortening the pin shortens the overlap with it: here the hero is
   still a fifth of the screen when it lets go, and simply scrolls the rest of
   the way out. */
@media (max-width: 720px) {
  .anim .pin { height: 180svh; }
  .anim .after { margin-top: -80svh; }
}

/* the doors sit with the supporting line, not at the far edge of the viewport */
.hero-b {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(20px, 2.6vh, 34px);
}

.doors { display: flex; align-items: center; gap: 18px; }

.door {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 1.02rem;
  font-weight: 500;
  letter-spacing: -.015em;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--hair-s);
  transition: border-color .4s var(--ease), font-weight .4s var(--ease);
}
.door .arw { width: 19px; height: 19px; transition: transform .5s var(--ease); }
.door:hover { border-bottom-color: var(--ink); }
.door:hover .arw { transform: translateX(6px); }
.door.big { font-size: clamp(1.02rem, 1.7vw, 1.4rem); }

/* the round, faded Instagram door — full strength only on approach */
.ig {
  display: grid;
  place-items: center;
  width: 46px; height: 46px;
  flex: none;
  border: 1px solid var(--hair-s);
  border-radius: 50%;
  opacity: .42;
  transition: opacity .4s var(--ease), background .4s var(--ease),
              color .4s var(--ease), border-color .4s var(--ease), transform .5s var(--ease);
}
.ig svg { width: 20px; height: 20px; }
.ig:hover, .ig:focus-visible {
  opacity: 1;
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
  transform: scale(1.06);
}

/* --- services: an editorial list, ruled — not cards ----------------------- */

.svc { margin-top: clamp(44px, 7vh, 86px); }

.svc-i {
  display: grid;
  grid-template-columns: minmax(0, 4fr) minmax(0, 8fr);
  gap: clamp(20px, 4vw, 64px);
  align-items: start;
  padding-block: clamp(28px, 4.2vh, 48px);
  border-top: 1px solid var(--hair);
}
.svc-i:last-child { border-bottom: 1px solid var(--hair); }

.svc-t {
  font-size: clamp(1.9rem, 4.4vw, 3.6rem);
  line-height: .95;
  letter-spacing: -.042em;
  font-weight: 500;
}
.svc-d {
  color: var(--mute);
  max-width: 52ch;
  font-size: clamp(1.02rem, .35vw + .93rem, 1.18rem);
  line-height: 1.5;
  letter-spacing: -.011em;
}

/* --- each service carries its own shape ----------------------------------- */

/* Four steps on a hairline. The rail draws itself along its own measure as the
   row arrives — the gesture every other hairline on this page already makes —
   and then something is put through it: one inked node travels the length and
   the last step takes the ink when it lands. It is the discipline described in
   the paragraph beside it, stated once more in the only graphic element this
   world has. */
.svc-b { display: flex; flex-direction: column; }

.flow {
  position: relative;
  max-width: 44ch;
  margin-top: clamp(22px, 2.6vh, 32px);
}

.flow-r {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* The nodes and the rail already say this is a sequence; a numeral in front of
   each step would be the same fact told twice. */
.flow-s {
  position: relative;
  padding-top: 22px;
  font-size: .72rem;
  font-weight: 500;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--mute);
}

.flow-s::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  border: 1px solid var(--hair-s);
  background: var(--paper);
  z-index: 1;
  transition: background .45s var(--ease), border-color .45s var(--ease);
}

/* The rail stops exactly where the next node starts, which is this column's own
   right edge — so the segment is the column and no arithmetic is needed. */
.flow-s::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 4px;
  right: 0;
  height: 1px;
  background: var(--hair);
  transform-origin: 0 50%;
}
.flow-s:last-child::after { content: none; }

/* The step is now a control. The appearance doesn't change: the button carries only accessibility. */
.flow-b {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  text-align: left;
  cursor: pointer;
  display: block;
  width: 100%;
  transition: color .35s var(--ease);
}
.flow-b:hover { color: var(--ink); }
.flow-b:focus-visible { outline: 1px solid var(--ink); outline-offset: 3px; }

/* A step the instrument has reached. All three instruments write this class;
   it is the one visual fact they share, so it lives with the shared grammar.
   The existing scroll-in rule inks only the last node — this inks whichever
   node the machine has actually got to. */
.flow-s.lit { color: var(--ink); }
.flow-s.lit::before { background: var(--ink); border-color: var(--ink); }

/* The scroll-in choreography inked the last node on a 2900ms delay and ran a
   token down the rail — correct when the diagram was a picture, wrong now that
   a machine drives it. On an instrumented row the machine owns the ink, so the
   legacy rules are switched off there. Rows without [data-proof] keep them. */
.anim .svc-i[data-proof].in .flow-s:last-child::before {
  background: var(--paper);
  border-color: var(--hair-s);
  transition-delay: 0s;
}
.anim .svc-i[data-proof].in .flow-s.lit::before {
  background: var(--ink);
  border-color: var(--ink);
}
.anim .svc-i[data-proof].in .flow-tok { animation: none; opacity: 0; }

/* The token is a runner carrying a dot at its leading corner, and the runner is
   sized to the travel itself: four equal steps put the last node at 75% of the
   rail, so a runner 75% long, moved by its own length, lands the dot exactly
   there. Nothing has to know the step count twice, no container units are
   needed, and the same declaration turns the corner when the rail does —
   width and translateX across, height and translateY down. */
.flow-tok {
  position: absolute;
  top: 0;
  left: 0;
  width: 75%;
  height: 7px;
  opacity: 0;
  z-index: 2;
  pointer-events: none;
}
.flow-tok::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ink);
}

/* Without animation the diagram is simply finished: rails whole, last step
   inked. Nothing here depends on a frame having run. */
.flow-s:last-child::before { background: var(--ink); border-color: var(--ink); }

.anim .flow-s:last-child::before { background: var(--paper); border-color: var(--hair-s); }
.anim .flow-s::after { transform: scaleX(0); transition: transform .75s var(--ease); }

/* The row's own arrival delay is inherited; each segment is then dealt after the
   one before it, so the rail reads as being laid rather than switched on. */
.anim .svc-i.in .flow-s::after { transform: scaleX(1); }
.anim .svc-i.in .flow-s:nth-child(1)::after { transition-delay: calc(var(--d, 0ms) + 240ms); }
.anim .svc-i.in .flow-s:nth-child(2)::after { transition-delay: calc(var(--d, 0ms) + 400ms); }
.anim .svc-i.in .flow-s:nth-child(3)::after { transition-delay: calc(var(--d, 0ms) + 560ms); }

.anim .svc-i.in .flow-tok {
  animation: flow-tok 2.4s var(--ease-io) calc(var(--d, 0ms) + 700ms) both;
}

/* 2956ms after the row lands: the beat the token reaches the last node. */
.anim .svc-i.in .flow-s:last-child::before {
  background: var(--ink);
  border-color: var(--ink);
  transition-delay: calc(var(--d, 0ms) + 2900ms);
}

@keyframes flow-tok {
  0%   { transform: translateX(0);    opacity: 0; }
  10%  {                              opacity: 1; }
  94%  { transform: translateX(100%); opacity: 1; }
  100% { transform: translateX(100%); opacity: 0; }
}

/* --- the proof instruments: triggers and the console they drive ----------- */

.trg {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: clamp(18px, 2.2vh, 26px);
}
.trg-b {
  appearance: none;
  background: none;
  border: 1px solid var(--hair);
  border-radius: 999px;
  padding: 6px 13px;
  font: inherit;
  /* the nav-phone step (.8rem), not the caption floor: this is a control the
     visitor presses, and every other control in the system sits at .8rem or
     above. .74rem was a step of its own worth 0.3px */
  font-size: .8rem;
  letter-spacing: .04em;
  color: var(--mute);
  cursor: pointer;
  transition: color .35s var(--ease), border-color .35s var(--ease), background .35s var(--ease);
}
.trg-b:hover { color: var(--ink); border-color: var(--hair-s); }
.trg-b:focus-visible { outline: 1px solid var(--ink); outline-offset: 3px; }
.trg-b[aria-pressed='true'] { color: var(--paper); background: var(--ink); border-color: var(--ink); }

.csl { margin-top: clamp(18px, 2.2vh, 26px); max-width: 44ch; }
/* Caption register, and nothing under it: an opacity on top of --mute was a
   second demotion of an already-demoted tone, and it composited to 2.90:1. The
   caption is quiet because it is small, tracked and set in capitals — not
   because its ink was faded a second time. */
.csl-cap {
  font-size: .72rem;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--mute);
  margin-bottom: 8px;
}

/* The three paths stack in one grid cell: the longest sets the height, so
   switching path never moves the page. */
.csl-sets { display: grid; }
.csl-set {
  grid-area: 1 / 1;
  list-style: none;
  margin: 0;
  padding: 0;
  visibility: hidden;
}
.csl-set[data-on] { visibility: visible; }

.csl-l {
  /* footnote step — mute supporting prose, read rather than labelled */
  font-size: .85rem;
  line-height: 1.75;
  color: var(--mute);
  border-left: 1px solid var(--hair);
  padding-left: 12px;
}
.csl-l:last-child { color: var(--ink); }

/* Without animation all four lines are simply written. */
.anim .csl-l { opacity: 0; transform: translateY(4px); }
.anim .csl-l.on {
  opacity: 1;
  transform: none;
  transition: opacity .4s var(--ease), transform .4s var(--ease);
}

/* The mark builds itself: three baselines are drawn, the leading tightens to the
   identity's own value, the three lines set flush left, the dot lands. Every
   moving piece is the logo file itself under a different clip, so the end state
   is the mark rather than a redrawing of it. The bands' clips cut through the
   empty gaps between the lines, at 32.78% and 68.27% of the 1000x1366.71 box. */
.bld {
  --mw: 79px;                  /* the mark at its own aspect, 1000/1366.71 */
  --mh: 108px;
  --top: 21px;
  --loose: 11px;               /* the leading before it tightens */
  --indent: 14px;              /* the offset before the lines set flush left */
  position: relative;
  max-width: 44ch;
  height: 150px;               /* reserved from the start: no stage moves the page */
  margin-top: clamp(18px, 2.2vh, 26px);
}

/* The baselines run the mark's own measure and a little past it — a rule under
   something, not a stray line crossing the row. */
.bld-g {
  position: absolute;
  left: 0;
  width: calc(var(--mw) + 52px);
  height: 1px;
  background: var(--hair);
  opacity: 0;
  transform-origin: 0 50%;
  transform: translateY(var(--dy, 0px)) scaleX(var(--sx, 0));
  transition: opacity .5s var(--ease), transform .7s var(--ease);
}
.bld-g:nth-child(1) { top: 53px; --dy: calc(var(--loose) * -1); }
.bld-g:nth-child(2) { top: 92px; }
.bld-g:nth-child(3) { top: 129px; --dy: var(--loose); }

.bld-mark,
.bld-ln,
.bld-dot {
  position: absolute;
  left: 0;
  top: var(--top);
  width: var(--mw);
  height: var(--mh);
  background: var(--ink);
  -webkit-mask: url('../img/eriscolabs.svg') left top / 100% 100% no-repeat;
          mask: url('../img/eriscolabs.svg') left top / 100% 100% no-repeat;
  opacity: 0;
  transition: opacity .5s var(--ease), transform .7s var(--ease);
}
.bld-ln,
.bld-dot { transform: translate(var(--dx, 0px), var(--dy, 0px)); }

/* er / is / co — each line clipped out of the one mark. The dot is cut away
   from the third line so it can land on its own beat. */
.bld-ln:nth-child(5) { clip-path: inset(0 0 67.22% 0); --dy: calc(var(--loose) * -1); }
.bld-ln:nth-child(6) { clip-path: inset(32.78% 0 31.73% 0); --dx: var(--indent); }
.bld-ln:nth-child(7) { clip-path: inset(68.27% 15% 0 0); --dx: calc(var(--indent) * 2); --dy: var(--loose); }
.bld-dot             { clip-path: inset(88% 0 0 85%); --dx: calc(var(--indent) * 2); --dy: var(--loose); }

/* 1 — the baselines are drawn and the lines land on them */
.bld[data-stage='1'] .bld-g,
.bld[data-stage='2'] .bld-g,
.bld[data-stage='3'] .bld-g { opacity: 1; --sx: 1; }
.bld[data-stage='1'] .bld-ln,
.bld[data-stage='2'] .bld-ln,
.bld[data-stage='3'] .bld-ln,
.bld[data-stage='4'] .bld-ln { opacity: 1; }

/* 2 — the leading tightens to the identity's value, rules and lines together */
.bld[data-stage='2'] .bld-g,
.bld[data-stage='3'] .bld-g,
.bld[data-stage='2'] .bld-ln,
.bld[data-stage='3'] .bld-ln,
.bld[data-stage='4'] .bld-ln,
.bld[data-stage='2'] .bld-dot,
.bld[data-stage='3'] .bld-dot,
.bld[data-stage='4'] .bld-dot { --dy: 0px; }

/* 3 — the lines set flush left */
.bld[data-stage='3'] .bld-ln,
.bld[data-stage='4'] .bld-ln,
.bld[data-stage='3'] .bld-dot,
.bld[data-stage='4'] .bld-dot { --dx: 0px; }

/* 4 — the dot lands, the grid goes, the mark itself remains */
.bld[data-stage='4'] .bld-g { opacity: 0; --sx: 1; --dy: calc(var(--loose) * -1); }
.bld[data-stage='4'] .bld-dot { opacity: 1; }

/* Without animation the mark is simply there, in one piece. */
:root:not(.anim) .bld .bld-g,
:root:not(.anim) .bld .bld-ln,
:root:not(.anim) .bld .bld-dot { opacity: 0; }
:root:not(.anim) .bld .bld-mark { opacity: 1; }

/* --- software: the site measures itself ------------------------------------ */

.mtr {
  margin-top: clamp(18px, 2.2vh, 26px);
  max-width: 44ch;
  display: grid;
  gap: 2px;
}
.mtr-r {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding-block: 7px;
  border-top: 1px solid var(--hair);
  font-size: .8rem;
  min-height: 32px;              /* reserved: counting up must not resize the row */
}
.mtr-r dt { color: var(--mute); flex: 1; }
.mtr-r dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}

.anim .mtr-r { opacity: 0; }
.anim .mtr-r.on { opacity: 1; transition: opacity .45s var(--ease); }

/* Draft: the same micro-typographic register as .ph-l and .flow-s. No new
   colour, no new shape — a hairline and the page's own small caps. */
.dft {
  font-size: .72rem;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--mute);
  border: 1px solid var(--hair);
  border-radius: 2px;
  padding: 1px 5px;
  white-space: nowrap;
}

/* --- work ----------------------------------------------------------------- */

.sec-hd {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
}

.more {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: .95rem;
  font-weight: 500;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--hair-s);
  transition: border-color .4s var(--ease), font-weight .4s var(--ease);
}
.more .arw { width: 17px; height: 17px; transition: transform .5s var(--ease); }
.more:hover { border-bottom-color: var(--ink); }
.more:hover .arw { transform: translateX(5px); }

.grid {
  margin-top: clamp(40px, 6vh, 76px);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
  gap: clamp(26px, 3vw, 46px) clamp(20px, 2.4vw, 38px);
}

/* placeholder frame — honest about being empty, still composed */
.ph {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 4 / 3;
  border: 1px solid var(--hair);
  background: var(--wash);
  overflow: hidden;
}
.ph::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--ink);
  opacity: 0;
  transition: opacity .5s var(--ease);
}
.ph-l {
  position: relative;
  z-index: 1;
  /* the same micro-label step as .prj-c — two near-identical sizes were a
     ramp step that carried no meaning */
  font-size: .72rem;
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--mute);
  transition: color .5s var(--ease);
}
/* once a real image lands in the frame: it uncovers, it never zooms —
   the hover stays the same ink wash every other frame uses */
.ph.is-img { background: none; border-color: var(--hair); }
.ph.is-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.anim .ph.is-img img {
  clip-path: inset(0 0 100% 0);
  transition: clip-path 1s var(--ease) var(--d, 0ms);
}
.anim .prj.in .ph.is-img img { clip-path: inset(0 0 0 0); }

/* An honest gap, composed: the hairline frame and the page's own numerals,
   not a grey rectangle apologising for a missing asset. */
.ph { background: none; border: 1px solid var(--hair); }
.ph-n {
  position: absolute;
  left: 14px;
  top: 12px;
  font-size: .8rem;
  font-variant-numeric: tabular-nums;
  color: var(--mute);
  letter-spacing: .04em;
}
/* --mute alone. The .72 opacity that used to sit here took this label from
   5.32:1 to 3.01:1 — the frame is already a hairline and a numeral, and the
   label does not need to be faded to read as trailing metadata. */
.ph-l { color: var(--mute); }

.ph.is-real { display: grid; place-items: center; border-color: var(--hair-s); }
.mark-l {
  width: 44%;
  aspect-ratio: 1;
  background: var(--ink);
  -webkit-mask: url('../img/eriscolabs.svg') center / contain no-repeat;
          mask: url('../img/eriscolabs.svg') center / contain no-repeat;
}
.prj-d {
  display: block;
  margin-top: 8px;
  color: var(--mute);
  font-size: .85rem;                /* footnote step, same as .csl-l / .start-d */
  line-height: 1.5;
  max-width: 46ch;
}
.prj-a { display: block; color: inherit; text-decoration: none; }

.prj-m {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  margin-top: 14px;
  padding-bottom: 2px;
}
.prj-n {
  font-size: 1.05rem;
  font-weight: 500;
  letter-spacing: -.02em;
}
.prj-c {
  font-size: .72rem;
  font-weight: 500;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--mute);
  white-space: nowrap;
}

/* --- work page ------------------------------------------------------------ */

.wp-hero {
  min-height: 0;
  padding-block: clamp(48px, 9vh, 104px) clamp(40px, 7vh, 76px);
  gap: clamp(22px, 3vh, 34px);
}
.wp-sec { padding-top: clamp(40px, 6vh, 64px); }

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(18px, 2.6vw, 38px);
  padding-bottom: 16px;
  border-bottom: 1px solid var(--hair);
}

.flt {
  position: relative;
  font-size: .95rem;
  font-weight: 500;
  letter-spacing: -.012em;
  color: var(--mute);
  padding-bottom: 4px;
  transition: color .35s var(--ease), opacity .3s var(--ease);
}
.flt::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: -17px;
  height: 1px;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .5s var(--ease);
}
.flt:hover { color: var(--ink); }
.flt.is-on { color: var(--ink); }
.flt.is-on::after { transform: scaleX(1); }

.wp-grid { margin-top: clamp(32px, 5vh, 60px); }
.wp-grid .prj[hidden] { display: none; }

/* Rhythm comes from vertical offset, not from frame shape. Two per row, every
   frame the same 4:3, with the right-hand column dropped — so the page has a
   cadence while the handover stays a single crop spec and no project image is
   silently cut to a different ratio. */
@media (min-width: 900px) {
  .wp-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: clamp(24px, 3vw, 56px);
    row-gap: clamp(34px, 4vw, 68px);
    align-items: start;
  }
  .wp-grid .prj:nth-child(2n) { margin-top: clamp(52px, 7.5vw, 124px); }

  /* filtering hides tiles but nth-child still counts them, so the script
     re-marks the surviving order and these must win */
  .wp-grid .prj.is-low { margin-top: clamp(52px, 7.5vw, 124px); }
  .wp-grid .prj.is-high { margin-top: 0; }
}

.empty {
  margin-top: clamp(40px, 6vh, 72px);
  color: var(--mute);
  font-size: 1rem;
}

/* --- closing ------------------------------------------------------------- */

.cta-s .lead { margin-top: clamp(22px, 3vh, 34px); }
.cta-s .doors { margin-top: clamp(34px, 5vh, 54px); }

/* How a project starts. Deliberately NOT the .flow grammar: that language now
   belongs to the instruments, and saying it twice would cheapen both. */
.start {
  list-style: none;
  margin: clamp(28px, 4vh, 44px) 0 0;
  padding: 0;
  display: grid;
  gap: 0;
  max-width: 62ch;
}
.start-s {
  display: grid;
  grid-template-columns: 2.4ch minmax(0, 12ch) minmax(0, 1fr) auto;
  gap: 14px;
  align-items: baseline;
  padding-block: 13px;
  border-top: 1px solid var(--hair);
}
.start-s:last-child { border-bottom: 1px solid var(--hair); }
.start-n { color: var(--mute); font-variant-numeric: tabular-nums; font-size: .8rem; }
.start-t { font-weight: 500; }
.start-d { color: var(--mute); font-size: .85rem; line-height: 1.5; }

@media (max-width: 640px) {
  .start-s { grid-template-columns: 2.4ch minmax(0, 1fr) auto; }
  .start-d { grid-column: 2 / -1; }
  /* .dft is the fourth, unpositioned child of .start-s — without an explicit
     column the row-major auto-placement cursor drops it into column 1,
     under the numeral, instead of under the text it qualifies. Grid's default
     justify-items: stretch then blows the blockified span out to fill the
     whole column, so justify-self: start is needed too — it shrink-wraps
     back to its text instead of becoming a wide near-empty box. Scoped to
     .start-s: the badge is shared with the measurement row, and a bare .dft
     here would hand grid placement to a badge sitting in a flex row the day
     that row becomes a grid. */
  .start-s .dft { grid-column: 2; justify-self: start; }
}

/* --- footer --------------------------------------------------------------- */

.ft {
  max-width: var(--max);
  margin-inline: auto;
  padding: 28px var(--gut) 40px;
  border-top: 1px solid var(--hair);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px 26px;
  font-size: .85rem;
  color: var(--mute);
}
.ft-l { display: flex; gap: 26px; flex-wrap: wrap; }
.ft a { transition: color .3s var(--ease); }
.ft a:hover { color: var(--ink); }

/* --- motion: one authored moment, then quiet ------------------------------ */

/* The mask box is padded so Turkish diacritics — the İ dot, the ş cedilla,
   the y descender — are not sliced off by the clip. Negative margins hand the
   space back so the layout position is unchanged. */
.ln {
  display: block;
  overflow: hidden;
  padding-block: .18em;
}
.ln > span { display: block; }
/* Hero üçlüsünün her satırı TEK satır kalır. Türkçe karşılık iki kelime
   ("Yapay Zekâ", 2026-08-25) ve display puntosunda sarıyordu; `.ln`in
   overflow:hidden'ı ikinci satırı kesip yarım bir kelime bırakıyordu. */
.hero .stack .ln > span { white-space: nowrap; }

/* Inside a stack the padding must be paid back, or the leading loosens. At the
   stack's outer edges it must NOT be — that space is the descender's, and
   reclaiming it drove the next element into the ink. */
.stack > .ln { margin-block: -.18em; }
.stack > .ln:first-child { margin-top: 0; }
.stack > .ln:last-child { margin-bottom: 0; }

/* 128%, not 105%: the span must clear the padding as well as the line box. */
.anim .ln > span {
  transform: translateY(128%);
  transition: transform var(--t) var(--ease) var(--d, 0ms);
}
.anim .ln.in > span { transform: translateY(0); }

.anim .rvu {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity .8s var(--ease) var(--d, 0ms), transform .8s var(--ease) var(--d, 0ms);
}
.anim .rvu.in { opacity: 1; transform: none; }

/* the grid gets its own gesture: each frame uncovers from its own top edge,
   and the caption follows once the frame has landed */
.anim .prj .ph {
  clip-path: inset(0 0 100% 0);
  transition: clip-path .95s var(--ease) var(--d, 0ms);
}
.anim .prj.in .ph { clip-path: inset(0 0 0 0); }
.anim .prj .prj-m {
  opacity: 0;
  transition: opacity .55s var(--ease) calc(var(--d, 0ms) + 260ms);
}
.anim .prj.in .prj-m { opacity: 1; }

/* hairlines draw along their own measure */
.anim .sec,
.anim .svc-i,
.anim .ft { border-image: none; }
.anim .sec::before { content: none; }

/* Every .svc-i also carries .rvu (below), and both rules set the `transition`
   shorthand on the same element at equal specificity — the one later in the
   file wins outright, silently dropping the other's properties rather than
   merging with them. That left .rvu's opacity/transform without a transition
   at all: the reveal snapped instead of fading. Owning the full list here,
   in the rule that wins the cascade anyway, is what keeps it from happening
   again the next time either rule is touched. */
.anim .svc-i {
  background-image: linear-gradient(var(--hair), var(--hair));
  background-repeat: no-repeat;
  background-size: 0% 1px;
  background-position: 0 0;
  border-top-color: transparent;
  transition: opacity .8s var(--ease) var(--d, 0ms), transform .8s var(--ease) var(--d, 0ms), background-size 1.1s var(--ease) var(--d, 0ms);
}
.anim .svc-i.in { background-size: 100% 1px; }
.anim .svc-i:last-child { border-bottom-color: transparent; }
.anim .svc-i:last-child.in { border-bottom-color: var(--hair); transition: border-color .5s var(--ease) .8s; }

/* --- a selection is not a scroll ------------------------------------------ */

/* Dragging a selection to the edge of the window makes the browser scroll, and
   a scroll is what every reveal above is listening for — so the sections being
   selected animate in underneath the selection, but only when the drag runs
   downward into content that has never been revealed. Dragged back upward the
   same gesture is silent, because there is nothing left to reveal. main.js
   marks the body for as long as a selection is actually being dragged, and
   here every reveal is cut: the content is simply there, and the only thing
   moving on the page is the selection itself.

   !important rather than source order: the reveal's transitions are spread
   across five rules, one of them at higher specificity (.svc-i:last-child.in),
   and this file has already lost a reveal once to two `transition` shorthands
   quietly overwriting each other. This has to win outright, from one place. */
.selecting .ln > span,
.selecting .rvu,
.selecting .svc-i,
.selecting .prj .ph,
.selecting .prj .prj-m { transition: none !important; }

/* --- the hero types itself out -------------------------------------------- */

/* Every character holds its final position from the first frame; only its ink
   arrives late. Nothing reflows as the line grows, and the sentence stays whole
   for screen readers, search and copy-paste — which a growing substring is not.
   Unscoped on purpose: `.ch` exists only once the script has split the line, so
   there is never a frame where an untyped letter is visible. */
.ch { color: transparent; }
/* Boşluk da bir .ch, ve inline-block içindeki tek boşluk sıfır genişliğe
   çöküyor: iki kelimelik bir satır ("Yapay Zekâ", 2026-08-25) bitişik
   yazılıyordu. `pre` boşluğu olduğu gibi tutar. */
.ch { white-space: pre; }
.ch.on { color: inherit; }

/* On the hero, typing is the reveal — the mask gesture stands down, and with it
   the clip that served it. That clip is not free: the slogan's measure is
   max-width, its sentence fills it to the last pixel, and the caret waiting at
   the end of it was being sliced off at the box edge. */
.tw .hero .ln { overflow: visible; }
.tw .hero .ln > span { transform: none; transition: none; }

/* Each headline word is one line by design — sized on mobile specifically so
   it never needs to wrap. But the typewriter splits it into one <span> per
   character with nothing, not even whitespace, between the tags, and WebKit
   still finds a break point there once the scroll-exit's letter-spacing
   growth (scroll.js) pushes the line wide enough: the last character (and the
   caret riding on it) lands on a line of its own. Nothing in this stack was
   ever meant to wrap, so nothing is allowed to. */
.display.stack > .ln > span { white-space: nowrap; }

/* The caret hangs off the last letter written, so moving it is a class change,
   not a DOM move. It stands on the baseline at cap height — placing it absolutely
   measures against the inline box, which is taller than the letters and left the
   rule overshooting them at display size. Ink rather than currentColor: the
   letter it rides on is transparent until the moment it is typed. */
.tw .hero .ch.cur::after {
  content: '';
  display: inline-block;
  vertical-align: baseline;
  width: .055em;
  min-width: 1px;
  height: .74em;
  background: var(--ink);
  /* the negative margin pays the width back, so a caret stepping letter to
     letter never nudges the line it is standing in */
  margin-left: .06em;
  margin-right: -.115em;
}

/* It holds steady while the line is still being written — a caret that blinks
   mid-word reads as a fault — and settles into a slow pulse once the last word
   has landed, where it stays for good. */
.tw-done .hero .ch.cur::after { animation: caret 1.25s ease-in-out infinite; }
@keyframes caret { 0%, 100% { opacity: 1 } 50% { opacity: 0 } }

/* the doors wait for the sentence to finish rather than compete with it */
.tw .hero .doors { opacity: 0; transition: opacity .7s var(--ease); }
.tw-done .hero .doors { opacity: 1; }

/* on a language swap the written line fades out with everything else, then is
   written again — it does not blank out on one frame while the rest crossfades */
.swapping .hero .ch { transition: color .3s var(--ease); }

/* language swap replays the mask reveal */
.swapping .ln > span { transition-duration: .3s; transform: translateY(-128%); }

/* text outside a masked line cross-fades on the same beat */
.svc-t, .svc-d, .prj-c, .door span, .more span, .flow-s { transition: opacity .3s var(--ease); }
.prj-n { transition: opacity .3s var(--ease); }
.ph-l { transition: color .5s var(--ease), opacity .3s var(--ease); }
.ft a { transition: color .3s var(--ease), opacity .3s var(--ease); }

.swapping .svc-t, .swapping .svc-d, .swapping .prj-n, .swapping .prj-c,
.swapping .ph-l, .swapping .door span, .swapping .more span, .swapping .flow-s { opacity: 0; }

/* the frame between states must not animate */
.no-t, .no-t *, .no-t *::before, .no-t *::after { transition: none !important; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

/* --- theme transition ----------------------------------------------------- */

/* Scoped to the theme change alone. The inversion is driven by hand — a circle
   animated out of the control that caused it — so the default cross-fade comes
   off both snapshots. A page change uses these same pseudo-elements and must
   not inherit a transition written for a different gesture. */
.vt-theme::view-transition-old(root),
.vt-theme::view-transition-new(root) { animation: none; mix-blend-mode: normal; }
.vt-theme::view-transition-old(root) { z-index: 1; }
.vt-theme::view-transition-new(root) { z-index: 2; }

/* --- navigation ----------------------------------------------------------- */

/* The two pages are one document as far as the reader is concerned. Without
   this, clicking Work threw the whole site away and rebuilt it — a white flash
   in the middle of a build whose entire argument is its own finish. Browsers
   without cross-document transitions simply navigate as they always did. */
@view-transition { navigation: auto; }

/* The chrome is the same object on both pages, so it is lifted out of the
   snapshot and stays put while the page between it changes. Left inside the
   root it would cross-fade with everything else, which is the flicker this is
   here to remove. */
.hd { view-transition-name: hd; }
.ft { view-transition-name: ft; }

@keyframes pg-out { to { opacity: 0; transform: translateY(-14px); } }
@keyframes pg-in { from { opacity: 0; transform: translateY(18px); } }

html:not(.vt-theme)::view-transition-old(root) { animation: pg-out .28s var(--ease-io) both; }
html:not(.vt-theme)::view-transition-new(root) { animation: pg-in .46s var(--ease) both; }

/* The blanket reduced-motion rule below reaches ::before and ::after, not these.
   They have to be named. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* --- the portal ----------------------------------------------------------- */

/* The panel below the hero does not arrive as an edge. `--feather` is written by
   the hero's exit and shrinks to nothing as the panel lands, so the soft front
   belongs to the passage and not to the finished page — the class is taken off
   at the end and the sheet is full-bleed and crisp again. */
.anim .after.is-portal {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 var(--feather, 0px));
  mask-image: linear-gradient(to bottom, transparent 0, #000 var(--feather, 0px));
}

/* The mouth of the tunnel. z-index 1 holds it over the hero and its field but
   under the panel at 2, so the panel closes over it as the reader comes through
   rather than the glow sitting on top of the page that follows. */
.portal {
  position: fixed;
  top: 50%;
  left: 50%;
  width: min(150vmax, 1900px);
  aspect-ratio: 1;
  z-index: 1;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -50%) scale(.5);
  background: radial-gradient(circle closest-side,
              var(--port-a) 0, var(--port-b) 34%, transparent 68%);
  will-change: transform, opacity;
}

/* Ink, like everything else that is not paper here — which is what lets one
   declaration read as a lit mouth on white and on black without a second
   colour being introduced to the system. */
:root { --port-a: rgba(0, 0, 0, .115); --port-b: rgba(0, 0, 0, .05); }
:root[data-theme='dark'] { --port-a: rgba(255, 255, 255, .15); --port-b: rgba(255, 255, 255, .062); }

/* --- the pointer layer ---------------------------------------------------- */

/* The native cursor is only given up once the script has actually put a ring on
   the page. Hiding it from here would leave a visitor with no cursor at all
   wherever the script failed to run. `cursor` inherits, so the root covers the
   document; links and buttons are named because their own UA rule would win. */
.pcur-on, .pcur-on a, .pcur-on button { cursor: none; }

/* A hairline, not a blob. The 1px rule is the only graphic element this world
   allows besides type, and the ring is held to it. */
.pcur {
  position: fixed;
  top: 0;
  left: 0;
  width: 34px;
  height: 34px;
  z-index: 90;
  pointer-events: none;
  border: 1px solid #fff;
  border-radius: 50%;
  /* Origin at the box's own corner, so the centring translate at the end of the
     transform list runs first and every rotate and scale after it turns about
     the ring's middle. */
  transform-origin: 0 0;
  /* difference against a page that is only ever black or white inverts exactly:
     the ring reads black on paper, white in the dark, and over type it cuts
     through the letter it crosses. No theme handling, no second colour. */
  mix-blend-mode: difference;
  opacity: 0;
  /* width and height are eased in the loop; only the corner and the press are
     left to CSS, and neither is written per frame */
  transition: opacity .32s var(--ease), border-radius .38s var(--ease),
              border-width .25s var(--ease);
  will-change: transform, width, height;
}
.pcur-live .pcur { opacity: 1; }
.pcur.is-down { border-width: 2px; }

@supports not (mix-blend-mode: difference) {
  .pcur { border-color: var(--ink); }
}

/* The lamp. A fixed square carrying a static gradient, moved by transform —
   painting a viewport-wide gradient at a new position every frame would repaint
   the whole screen for a wash nobody is meant to notice. */
.plamp {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 45;
  pointer-events: none;
  border-radius: 50%;
  transform-origin: 0 0;
  opacity: 0;
  background: radial-gradient(circle closest-side,
              var(--lamp-a), var(--lamp-b) 42%, transparent 72%);
  transition: opacity .7s var(--ease);
  will-change: transform;
}
.pcur-live .plamp { opacity: 1; }

/* Faint enough to be felt rather than seen. Dark paper takes more, because a
   pale wash on black reads weaker than the same wash of ink on white. */
:root { --lamp-a: rgba(0, 0, 0, .045); --lamp-b: rgba(0, 0, 0, .02); }
:root[data-theme='dark'] { --lamp-a: rgba(255, 255, 255, .08); --lamp-b: rgba(255, 255, 255, .035); }

/* A magnetised link answers with the ring alone. It was answering in weight as
   well, which is exactly the defect the fixed-advance rule exists to prevent:
   the door is an inline-flex label beside an arrow, so a heavier label pushes
   the arrow, and `.svc-t` runs to 3.6rem where the shift is a reflow. The
   element's own hover state — the rule darkening, the arrow travelling — is
   already the answer; the ring is the addition. */

/* --- the hero's letters tilt ---------------------------------------------- */

/* A transform needs a box, and an inline span has none. Scoped to the display
   stack, where every line is a single word: the slogan's characters are left
   inline, because turning its spaces into inline-blocks would change how the
   sentence breaks. */
.tw .hero .display .ch { display: inline-block; }

/* Layers only while the pointer is in the block. The loop hands each character
   back to the document the moment its spring settles, so a line at rest carries
   no compositor layer at all. */
.tw .hero .display.is-kin .ch { will-change: transform; }

/* --- responsive ----------------------------------------------------------- */

@media (max-width: 900px) {
  .svc-i { grid-template-columns: 1fr; gap: 12px; }
  .lead { max-width: 34ch; }
  .hero-b { align-items: flex-start; flex-direction: column; }
}

@media (max-width: 620px) {
  /* the contact button is a hard requirement — it shrinks here, it never leaves */
  .cta { padding: 7px 13px; font-size: .8rem; }
  .hd-nav { gap: 12px; }
  .nav-l { font-size: .8rem; }
  .ico { width: 22px; height: 22px; }
  .ico svg { width: 17px; height: 17px; }
  .mark { width: 25px; }

  /* Scale with the viewport so the stack reaches the gutter on every phone.
     Sized for the longest word in the stack — "Automation." is 11 characters,
     and at 320px anything above ~16vw runs past the gutter. The selector has
     to match `.hero .display` or it loses on specificity, media query or not. */
  .display,
  .hero .display { font-size: clamp(2.9rem, 15.5vw, 4.6rem); letter-spacing: -.042em; }
  .display.sm { font-size: clamp(2.4rem, 12vw, 3.6rem); }
  .doors { gap: 14px; }
  .ig { width: 42px; height: 42px; }

  /* the hero earns its height from content here, not from the viewport */
  .hero {
    min-height: 0;
    padding-block: clamp(40px, 7vh, 64px) clamp(64px, 11vh, 96px);
    gap: 32px;
  }

  .ft {
    flex-direction: column;
    align-items: flex-start;
    gap: 14px;
  }
  .ft-l { gap: 8px; flex-direction: column; }

  /* Four steps will not share a phone's measure. Measured at 320px, the Turkish
     "Araştırma" runs past its column at the caption step however the tracking is
     set — and shrinking the type below that step would be inventing a size the
     system does not have, to serve one label. The sequence turns instead: the
     rail runs down the page and every step gets the full width, so the caption
     size stands exactly as documented.

     The gap is paid as padding inside each step rather than as `row-gap`, which
     is what keeps the four rows exactly equal — the token's travel is written as
     a percentage of the rail, and that only lands on the last node if the nodes
     are evenly spaced. */
  .flow-r { grid-auto-flow: row; grid-auto-columns: auto; }
  .flow-s { padding: 0 0 18px 20px; }
  .flow-s::before { top: 2px; }

  /* from this node's underside down to exactly where the next one begins */
  .flow-s::after {
    top: 9px;
    left: 3px;
    right: auto;
    width: 1px;
    height: calc(100% - 7px);
    transform-origin: 50% 0;
  }

  .anim .flow-s::after { transform: scaleY(0); }
  .anim .svc-i.in .flow-s::after { transform: scaleY(1); }

  /* The runner turns with the rail: it is now 7px across and 75% down, and it
     is still moved by exactly its own length. */
  .flow-tok { top: 2px; width: 7px; height: 75%; }
  .anim .svc-i.in .flow-tok { animation-name: flow-tok-v; }

  @keyframes flow-tok-v {
    0%   { transform: translateY(0);    opacity: 0; }
    10%  {                              opacity: 1; }
    94%  { transform: translateY(100%); opacity: 1; }
    100% { transform: translateY(100%); opacity: 0; }
  }
}

/* --- the consent gate ------------------------------------------------------
   A ruled strip at the foot of the page, in the one graphic element this
   system has. Solid paper rather than a blur, and a hairline rather than a
   shadow, for the same reason the sticky header is: those are the only two
   devices here. It never dims the page behind it — the visitor is not being
   held hostage for an answer, and the site works fine without one. */

.cc {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 80;                 /* over the sticky header's 50, under the ring's 90 */
  background: var(--paper);
  border-top: 1px solid var(--hair);
  padding: 18px var(--gut);
}
.cc[hidden] { display: none; }

.cc-in {
  max-width: var(--max);
  margin-inline: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 14px 32px;
}

.cc-txt { max-width: 62ch; }

/* The 2026-02-18 KVKK principle decision requires the notice (aydınlatma)
   and the consent request (açık rıza) to read as two separate texts under
   two separate headings, not one merged paragraph — even inside a banner
   this small. These eyebrow labels are that separation, reusing the same
   tracked-caps convention as .csl-cap elsewhere on the site. */
.cc-eb {
  font-size: .72rem;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--mute);
  margin-bottom: 4px;
}
.cc-eb2 { margin-top: 10px; }

.cc-t, .cc-r {
  font-size: .92rem;
  line-height: 1.5;
  color: var(--mute);
}
.cc-t a { color: var(--ink); border-bottom: 1px solid var(--hair-s); }

/* Both answers are the same control. Not "the same size" — literally one rule,
   so no future edit can quietly make refusing the harder of the two. A refusal
   that costs more than a consent is not a freely given consent. */
.cc-a { display: flex; gap: 12px; flex-wrap: wrap; }
.cc-b {
  appearance: none;
  background: none;
  font: inherit;
  font-size: .92rem;
  color: var(--ink);
  cursor: pointer;
  padding: 9px 18px;
  border: 1px solid var(--hair-s);
  border-radius: 100px;
  white-space: nowrap;
  transition: background .35s var(--ease), color .35s var(--ease), border-color .35s var(--ease);
}
.cc-b:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.cc-b:focus-visible { outline: 1px solid var(--ink); outline-offset: 3px; }

@media (max-width: 620px) {
  .cc { padding: 15px var(--gut); }
  .cc-in { gap: 12px; }
  .cc-a { width: 100%; }
  .cc-b { flex: 1; text-align: center; }
}

/* --- the legal page --------------------------------------------------------
   No new register: the same display, lead, body and hairline the rest of the
   site is set in. A policy that looks like it came from somewhere else reads
   like it was written by someone else. */

.lgl { max-width: 74ch; }
.lgl h3 {
  margin-top: 46px;
  font-size: clamp(1.15rem, .5vw + 1rem, 1.35rem);
  font-weight: 500;
  letter-spacing: -.02em;
}
.lgl h3:first-of-type { margin-top: 34px; }
.lgl p, .lgl li {
  margin-top: 14px;
  color: var(--mute);
  font-size: clamp(1rem, .3vw + .92rem, 1.1rem);
  line-height: 1.6;
}
.lgl ul { margin-top: 4px; padding-left: 0; list-style: none; }
.lgl li { padding-left: 18px; position: relative; }
.lgl li::before {
  content: '';
  position: absolute;
  left: 0;
  top: .72em;
  width: 7px;
  height: 1px;
  background: var(--hair-s);
}
.lgl a { color: var(--ink); border-bottom: 1px solid var(--hair-s); }
.lgl .upd { margin-top: 40px; padding-top: 14px; border-top: 1px solid var(--hair); font-size: .85rem; }

/* The cookie table. Rows on hairlines, like every other list here. */
.ck { margin-top: 18px; width: 100%; border-collapse: collapse; font-size: .88rem; }
.ck th, .ck td {
  text-align: left;
  vertical-align: top;
  padding: 11px 14px 11px 0;
  border-top: 1px solid var(--hair);
  color: var(--mute);
  font-weight: 400;
}
.ck th { color: var(--ink); font-size: .72rem; text-transform: uppercase; letter-spacing: .13em; }
.ck td:first-child { color: var(--ink); white-space: nowrap; }
.ck-w { overflow-x: auto; }

/* --- faq -------------------------------------------------------------------
   Native <details>, no script: an accordion the browser already knows how to
   run, in the register the rest of the site is set in — hairlines between
   rows like .svc-i, weight over colour for the open/closed state, a plain
   "+"/"–" glyph instead of a drawn icon. */

.faq { margin-top: clamp(28px, 4vh, 48px); max-width: 74ch; }
.faq-i { border-top: 1px solid var(--hair); }
.faq-i:last-child { border-bottom: 1px solid var(--hair); }

.faq-q {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
  padding-block: clamp(18px, 2.6vh, 26px);
  font-size: clamp(1.05rem, .4vw + .95rem, 1.25rem);
  font-weight: 500;
  letter-spacing: -.015em;
  cursor: pointer;
  list-style: none;
}
.faq-q::-webkit-details-marker { display: none; }
.faq-q::after {
  content: '+';
  flex: none;
  color: var(--mute);
  font-weight: 400;
  font-size: 1.3em;
  line-height: 1;
}
.faq-i[open] .faq-q::after { content: '–'; color: var(--ink); }
.faq-q:focus-visible { outline: 1px solid var(--ink); outline-offset: 3px; }

.faq-a {
  max-width: 62ch;
  padding-bottom: clamp(18px, 2.6vh, 26px);
  color: var(--mute);
  font-size: clamp(1rem, .3vw + .92rem, 1.1rem);
  line-height: 1.6;
}
