/* ==========================================================================
   Almog Shalom — Portfolio
   Design reference: Figma "Marketing design" (1920px artboards)
   ========================================================================== */

/* ---------- Tokens ---------- */
:root {
  --bg:        #EAEAEA;
  --fg:        #000000;
  --muted:     #989898;
  --line:      #000000;

  --page-max:  1920px;
  --pad-x:     32px;
  --gutter:    8px;

  /* Fluid type — hits the Figma values exactly at 1920px */
  --t-48: clamp(32px, 2.50vw, 48px);
  --t-28: clamp(20px, 1.46vw, 28px);
  --t-24: clamp(18px, 1.25vw, 24px);
  --t-20: clamp(16px, 1.04vw, 20px);
  --t-16: clamp(14px, 0.84vw, 16px);

  --tracking: 0.02em;   /* 2% — matches every Figma text style */
}

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: 'General Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
  font-weight: 500;
  letter-spacing: var(--tracking);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img, svg { display: block; max-width: 100%; }
p, figure, h1, h2, h3, blockquote { margin: 0; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }

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

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

/* ---------- Page shell ---------- */
.page {
  max-width: var(--page-max);
  margin-inline: auto;
  overflow-x: clip;
}

.shell { padding-inline: var(--pad-x); }

/* ---------- Type helpers ---------- */
.t-name     { font-weight: 600; font-size: var(--t-48); line-height: 1; }
.t-wordmark { font-weight: 600; font-size: var(--t-28); line-height: 1; white-space: nowrap; }
.t-role     { font-weight: 500; font-size: var(--t-24); line-height: 1; }
.t-body     { font-weight: 500; font-size: var(--t-20); line-height: 1.5; }
.t-label    { font-weight: 400; font-size: var(--t-20); line-height: 1; color: var(--muted); }
.t-link     { font-weight: 500; font-size: var(--t-16); line-height: 1; }
.t-cap      { font-weight: 400; font-size: var(--t-16); line-height: 1; color: var(--muted); }

/* ==========================================================================
   Header
   ========================================================================== */
.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: var(--pad-x);
  background: var(--bg);
}

.site-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex: 0 1 720px;
  min-width: 0;
  gap: 24px;
}

.nav-link { position: relative; white-space: nowrap; }
.nav-link::after {
  content: "";
  position: absolute;
  left: 0; bottom: -4px;
  width: 100%; height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .3s ease;
}
.nav-link:hover::after,
.nav-link[aria-current="page"]::after { transform: scaleX(1); }

/* Download CV — the trigger downloads the CV in the language the page is
   reading, so the menu under it only has to offer the other one: one item,
   named in the reader's own language. */
.cv-menu { position: relative; }

.cv-menu__list {
  position: absolute;
  top: calc(100% + 12px);
  /* The item is wider than the trigger and the trigger sits at the end of the
     header, so the menu hangs from its end edge and grows inward — from the
     start edge it would run off the screen. */
  inset-inline-end: 0;
  z-index: 50;
  display: flex;
  flex-direction: column;
  min-width: 100%;
  background: var(--bg);
  border: 1px solid var(--line);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity .2s ease, transform .2s ease, visibility 0s linear .2s;
}
/* Bridges the gap to the trigger, so the pointer doesn't drop the menu on its way down */
.cv-menu__list::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: 100%;
  height: 13px;
}
.cv-menu:hover .cv-menu__list,
.cv-menu:has(:focus-visible) .cv-menu__list {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition-delay: 0s;
}
.cv-menu:hover .nav-link::after { transform: scaleX(1); }

.cv-menu__item {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 16px;
  white-space: nowrap;
  transition: background-color .2s ease, color .2s ease;
}
.icon-download { width: 1.15em; height: 1.15em; flex: none; stroke-width: 1.75; }
.cv-menu__item:hover,
.cv-menu__item:focus-visible { background: var(--fg); color: var(--bg); outline: none; }

/* On About the trigger is the full-width button, and the menu matches its width */
.cv-menu--block { width: 100%; max-width: 777px; }
.cv-menu--block .btn { max-width: none; }
.cv-menu--block .cv-menu__list { inset-inline: 0; top: calc(100% - 1px); }
.cv-menu--block .cv-menu__list::before { display: none; }
.cv-menu--block .cv-menu__item { padding: 16px 12px; }
.cv-menu--block:hover .btn { background: var(--fg); color: var(--bg); }

/* Burger (mobile only) — Tabler's menu-2 and x, the set the download arrow
   already comes from. Both sit in the same grid cell and cross-fade, so the
   button never changes size between the two states. */
.nav-toggle { display: none; place-items: center; width: 28px; height: 28px; flex: none; color: var(--fg); }
.nav-toggle svg {
  grid-area: 1 / 1;
  width: 28px; height: 28px;
  stroke-width: 1.5;
  transition: opacity .25s ease;
}
.nav-toggle .icon-close { opacity: 0; }
body.nav-open .nav-toggle .icon-menu { opacity: 0; }
body.nav-open .nav-toggle .icon-close { opacity: 1; }

/* ==========================================================================
   Home — intro
   ========================================================================== */
.intro {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 32px;
  padding: 100px var(--pad-x) 40px;
}

.intro__id {
  display: flex;
  flex-direction: column;
  gap: 12px;
  flex: 0 0 auto;
}

/* 1038px is the Figma width — the paragraph keeps it and the name block,
   whose text is far narrower, gives up the slack instead. */
.intro__text {
  flex: 0 1 1038px;
  min-width: 0;
  text-align: end;
  /* Not balanced: balancing evens the lines out, which leaves the paragraph
     short of its own column and puts the longest line last — the opposite of
     how a ragged edge should fall. Let it fill the width and end short. */
  text-wrap: normal;
}

/* The home bio runs to three lines. Sized in em so the break holds as the type
   scales with the viewport: on one line the English (at 500) runs ~124em and
   the Hebrew (at 400) ~89em, so each gets a width between a third and a half
   of its own length. */
/* Set at the header links' size. */
.intro--home .intro__text { font-size: var(--t-16); }
.intro--home .intro__text p { max-width: 48em; margin-inline-start: auto; }
:root[lang="he"] .intro--home .intro__text p { max-width: 38em; }

/* ==========================================================================
   Home — the three projects, one card each (634.67 × 846 in Figma). On hover
   (or keyboard focus) the card takes the brand's colour and the logo turns to
   the version that sits on it.
   ========================================================================== */
.home-grid { gap: var(--gutter); }
.pcard {
  display: grid; place-items: center;
  aspect-ratio: 634.67 / 846;
  background: #fff;
  transition: background-color .35s ease;
}
.pcard__logo { width: 60.6%; height: auto; }   /* every logo sits in a 640×200 box at one scale */
.pcard__mark, .pcard__word { transition: fill .35s ease; }
.pcard--yokey  .pcard__mark, .pcard--yokey  .pcard__word { fill: #000; }
.pcard--ching  .pcard__mark, .pcard--ching  .pcard__word { fill: #0E423E; }
.pcard--glance .pcard__mark { fill: #8575FF; }
.pcard--glance .pcard__word { fill: #222222; }
.pcard--yokey:hover,  .pcard--yokey:focus-visible  { background: #000; }
.pcard--yokey:hover  .pcard__mark, .pcard--yokey:hover  .pcard__word,
.pcard--yokey:focus-visible .pcard__mark, .pcard--yokey:focus-visible .pcard__word { fill: #DAFF7D; }
.pcard--ching:hover,  .pcard--ching:focus-visible  { background: #8DDB90; }
.pcard--glance:hover, .pcard--glance:focus-visible { background: #8575FF; }
.pcard--glance:hover .pcard__mark, .pcard--glance:hover .pcard__word,
.pcard--glance:focus-visible .pcard__mark, .pcard--glance:focus-visible .pcard__word { fill: #E5E5E5; }

/* ==========================================================================
   Project page — title and one-line brief (the home intro's layout), and at
   the end a link to the project before and after (Figma: 200px in from each
   edge at 1920, 40px above and below).
   ========================================================================== */
.pnav {
  display: flex; justify-content: space-between; align-items: center; gap: 24px;
  padding: 40px 10.4167%;
}
.pnav__link { display: inline-flex; align-items: center; gap: 12px; line-height: 1; }
.pnav__arrow { width: 24px; height: 24px; flex: none; transition: transform .3s ease; }
.pnav__link--prev:hover .pnav__arrow, .pnav__link--prev:focus-visible .pnav__arrow { transform: translateX(-4px); }
.pnav__link--next:hover .pnav__arrow, .pnav__link--next:focus-visible .pnav__arrow { transform: translateX(4px); }

/* ==========================================================================
   Project grid
   ========================================================================== */
.work { display: flex; flex-direction: column; gap: var(--gutter); scroll-margin-top: 80px; }

.row { display: grid; gap: var(--gutter); }
.row--2   { grid-template-columns: repeat(2, 1fr); }
.row--3   { grid-template-columns: repeat(3, 1fr); }
.row--1   { grid-template-columns: 1fr; }

.cell { position: relative; overflow: hidden; background: var(--bg); }
.cell > img,
.cell > picture { width: 100%; height: 100%; }
.cell > picture { display: block; }
.cell > img,
.cell > picture > img { width: 100%; height: 100%; object-fit: cover; }

.cell--span2 { grid-column: span 2; }

/* Poster rows: one wide image, or on phones the four posters two by two */
.cell--posters { background: var(--posters-bg); }
.posters__grid { display: none; }

/* Aspect ratios lifted 1:1 from the Figma cells */
.ar-half   { aspect-ratio: 956 / 800; }        /* half-width photo      */
.ar-third  { aspect-ratio: 634.67 / 800; }     /* third-width photo     */
.ar-square { aspect-ratio: 1 / 1; }            /* construction grid     */
.ar-band   { aspect-ratio: 1277.33 / 634.67; } /* two-third logo band   */
.ar-full   { aspect-ratio: 1920 / 800; }       /* full-bleed billboard  */
.ar-strip  { aspect-ratio: 1920 / 400; }       /* full-bleed icon strip */

/* ==========================================================================
   About page
   ========================================================================== */
.about {
  display: grid;
  grid-template-columns: 39% minmax(0, 1fr);
  column-gap: 5.2%;
  padding-top: 100px;
}

/* Square, and never stretched by a taller text column */
.about__portrait { aspect-ratio: 1 / 1; align-self: start; overflow: hidden; }
.about__portrait img { width: 100%; height: 100%; object-fit: cover; }

/* The column stretches to the portrait's height so the button can sit on its
   bottom edge. The 244px of the Figma artboard is the slack at 1920px — below
   that it closes up and the blocks ride up with the button. */
.about__body {
  min-width: 0;
  max-width: 786px;
  display: flex;
  flex-direction: column;
  gap: 32px;
}

.about__lede { display: flex; flex-direction: column; gap: 20px; }
.about__blocks { margin-top: auto; display: flex; flex-direction: column; gap: 48px; }
.about__block { display: flex; flex-direction: column; gap: 20px; }

/* These paragraphs are hand-broken at chosen words. Where a segment is too long
   for the column it still has to wrap, and balancing it splits the overflow
   evenly instead of stranding one word on a line of its own. */
.about__block .t-body p { text-wrap: balance; }

.btn {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px 12px;
  border: 1px solid var(--line);
  width: 100%;
  max-width: 777px;
  transition: background-color .25s ease, color .25s ease;
}
.btn:hover { background: var(--fg); color: var(--bg); }

/* ==========================================================================
   Footer
   ========================================================================== */
.site-footer {
  display: flex;
  flex-direction: column;
  gap: 48px;
  padding: 100px var(--pad-x) 32px;
  background: var(--bg);
}

.footer__cols {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 40px;
  width: 1257px;
  max-width: 100%;
}

.footer__col { display: flex; flex-direction: column; gap: 28px; }
.footer__list { display: flex; flex-direction: column; gap: 20px; }
.footer__list a { transition: opacity .2s ease; }
.footer__list a:hover { opacity: .55; }

/* ==========================================================================
   Glance icon strip — the nine marks in a row (see "Glance icons" below)
   ========================================================================== */
.cell--icons { background: #222222; }
.icon-strip { width: 100%; height: 100%; }

/* ==========================================================================
   Website bands — a screenshot of each brand's site centred on a flat brand
   colour in a 1920-wide band. Figma has it 1140 wide; it's drawn a little
   larger here (--web-w). --web-r is the corner radius at the Figma size, so it
   is scaled by the same amount; container units keep it in proportion. On phones
   the band grows to leave more room above and below the screenshot.
   ========================================================================== */
.cell--web { --web-w: 1300; background: var(--web-bg); display: grid; place-items: center; container-type: inline-size; }
.cell--web > .web-shot {
  width: calc(var(--web-w) / 1920 * 100%); height: auto;
  border-radius: calc(var(--web-r) * var(--web-w) / 1140 / 1920 * 100cqw);
  object-fit: initial;
}

/* Ching's band carries a screen recording rather than a screenshot: someone
   moving through the site, stopping on the feature tabs, the pricing plans and
   the questions, and clicking each one. It sits in a window cut to the same
   proportions as the other bands' shots. The video only runs while the band is
   on screen (assets/js/web-clip.js), and falls back to its poster — the site's
   hero — when it can't. */
/* The band is a link to the live site. The tip says so on hover, because a
   screenshot that turns out to be clickable is otherwise something you find by
   accident. It is decoration — the link carries its own label for screen
   readers — so it stays out of the pointer's way. */
.web-clip { display: block; position: relative; overflow: hidden; }
.web-clip > video { display: block; width: 100%; height: auto; }

/* The project name and the button that opens the live site sit on one line.
   Every icon on the site comes from Tabler: 24-unit box, 2-unit stroke, round
   caps and joins, drawn in currentColor. */
.project-title { display: flex; align-items: center; gap: 14px; }

.site-link {
  display: grid;
  place-items: center;
  flex: none;
  color: inherit;
  /* the padding is only there to give the bare icon a comfortable target; the
     negative margin takes it back out of the layout so the icon still sits on
     the name's baseline block */
  padding: 7px;
  margin: -7px;
  transition: opacity .25s ease;
}
.site-link svg { width: clamp(22px, 1.6vw, 30px); height: clamp(22px, 1.6vw, 30px); }
.site-link:hover { opacity: .55; }
.site-link:focus-visible { outline: 2px solid var(--fg); outline-offset: 2px; }

@media (max-width: 767px) {
  .project-title { gap: 12px; }
}

.web-clip__icon { width: 15px; height: 15px; flex: none; }

.web-clip__tip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  position: absolute;
  inset-inline-start: 50%;
  bottom: 18px;
  transform: translate(-50%, 10px);
  padding: 9px 16px;
  border-radius: 6px;
  background: rgba(18, 18, 18, .82);
  color: #fff;
  font-size: clamp(12px, 0.73vw, 14px);
  font-weight: 500;
  letter-spacing: .01em;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity .25s ease, transform .25s ease;
}
.web-clip:hover .web-clip__tip,
.web-clip:focus-visible .web-clip__tip { opacity: 1; transform: translate(-50%, 0); }

/* RTL flips the translate, so the tip has to be centred the other way round */
[dir="rtl"] .web-clip__tip { transform: translate(50%, 10px); }
[dir="rtl"] .web-clip:hover .web-clip__tip,
[dir="rtl"] .web-clip:focus-visible .web-clip__tip { transform: translate(50%, 0); }

.web-clip:focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; }

@media (prefers-reduced-motion: reduce) {
  .web-clip__tip { transition: none; }
}

/* ==========================================================================
   Glance — the modules, two at a time. Five pairs (Figma frames 1–5) take
   turns (assets/js/glance-showcase.js), each staying up exactly as long as its
   two icons are moving; each icon runs the same animation it has in the strip.
   ========================================================================== */
.cell--gpairs { background: #8575FF; container-type: inline-size; }
.gpairs { position: absolute; inset: 0; }
.gpair {
  position: absolute; inset: 0;
  display: grid; grid-template-columns: 1fr 1fr;
  opacity: 0; transition: opacity .6s ease;
}
.gpair.is-active { opacity: 1; }
/* before the script has run (or without it), the first pair is up */
.cell--gpairs:not(.is-live) .gpair[data-i="1"] { opacity: 1; }
.gpair__half { position: relative; overflow: hidden; }
.gpair__art {
  position: absolute; inset: 0; width: 100%; height: 100%;
  transform: translateY(1.5%); transition: transform .7s cubic-bezier(.16, 1, .3, 1);
}
.gpair.is-active .gpair__art, .cell--gpairs:not(.is-live) .gpair[data-i="1"] .gpair__art { transform: none; }
.gpair__name { transform-box: fill-box; transform-origin: 50% 0; }


/* ==========================================================================
   Glance icons — the nine module marks, animated the same way wherever they
   appear (the icon strip and the module pairs).

   Each mark plays a short event and then rests, instead of wobbling forever:
   anticipation, the move itself, an overshoot, a settle. The move says what the
   module does, and it is made from the mark's own pieces (.icp--n, in the
   order Figma draws them) moving separately and slightly out of step, so it
   reads as a mechanism rather than a sticker being shaken. Nothing is ever
   stretched: pieces only slide and turn, so the geometry stays exactly as
   drawn. Where a mark ends turned (sun 45°, arrows 90°, frame 180°), it lands
   on its own symmetry, so the jump back to the start of the loop is invisible.

   Distances are in the pairs' units (a mark there is ~400 across) times --u,
   so the strip, drawn at .2725 of that size, moves in exact proportion.
   Timing is one 4.2s cycle everywhere; each context sets --d, the delay:
   in the strip the marks go one after another, left to right, as a wave; in
   the pairs the mark starts as its pair fades in, and the next pair comes up
   the moment the last of the two marks comes to rest.
   ========================================================================== */
.ic, .icp { transform-box: fill-box; transform-origin: 50% 50%; will-change: transform; }

.cell--icons  { --u: .2725px; }
.cell--gpairs { --u: 1px; --d: .15s; }
.gip--1 { --d: 0s; }    .gip--2 { --d: .18s; }  .gip--3 { --d: .36s; }
.gip--4 { --d: .54s; }  .gip--5 { --d: .72s; }  .gip--6 { --d: .9s; }
.gip--7 { --d: 1.08s; } .gip--8 { --d: 1.26s; } .gip--9 { --d: 1.44s; }

/* Nothing moves until the strip is on screen / until the pair is up */
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) :is(.ic, .icp) {
  animation-duration: 4.2s;
  animation-delay: calc(var(--d) + var(--pd, 0s));
  animation-iteration-count: infinite;
  animation-fill-mode: both;
}
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) .ic--G { animation-name: ic-g; }
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) .ic--crop   .icp { animation-name: ic-crop; }
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) .ic--chev   .icp { animation-name: ic-chev; }
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) .ic--x      .icp--1,
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) .ic--x      .icp--4 { animation-name: ic-x-v; }
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) .ic--x      .icp--2,
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) .ic--x      .icp--3 { animation-name: ic-x-h; }
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) .ic--sun { animation-name: ic-sun; }
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) .ic--sun    .icp { animation-name: ic-ray; }
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) .ic--tee    .icp--3 { animation-name: ic-tee-bar; }
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) .ic--tee    .icp--1,
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) .ic--tee    .icp--2 { animation-name: ic-tee-leg; }
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) .ic--arrows { animation-name: ic-arrows; }
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) .ic--arrows .icp { animation-name: ic-arrow; }
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) .ic--frame { animation-name: ic-frame; }
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) .ic--frame  .icp--1,
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) .ic--frame  .icp--2 { animation-name: ic-frame-c; }
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) .ic--frame  .icp--3 { animation-name: ic-frame-dot; }
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) .ic--roof   .icp--2,
:is(.cell--icons.is-in, .cell--gpairs.is-in .gpair.is-active) .ic--roof   .icp--3 { animation-name: ic-leg; }

/* Per-piece direction (--sx/--sy, or a unit vector --dx/--dy) and offset (--pd) */
.ic--crop .icp--1 { --sx: 1;  --sy: -1; }                 /* ┐ out to the top right  */
.ic--crop .icp--2 { --sx: -1; --sy: 1;  --pd: .06s; }     /* └ out to the bottom left */
.ic--chev .icp--1 { --pd: .14s; }                         /* the back chevron follows */
.ic--x .icp--1 { --sy: 1; }  .ic--x .icp--4 { --sy: -1; } /* bottom / top chevron     */
.ic--x .icp--2 { --sx: 1; }  .ic--x .icp--3 { --sx: -1; } /* left / right bar, inward */
/* sun rays, clockwise from the top, each 50ms after the last */
.ic--sun .icp--1 { --dx: 0;     --dy: -1;    --pd: 0s;   }
.ic--sun .icp--5 { --dx: .707;  --dy: -.707; --pd: .05s; }
.ic--sun .icp--3 { --dx: 1;     --dy: 0;     --pd: .1s;  }
.ic--sun .icp--7 { --dx: .707;  --dy: .707;  --pd: .15s; }
.ic--sun .icp--2 { --dx: 0;     --dy: 1;     --pd: .2s;  }
.ic--sun .icp--6 { --dx: -.707; --dy: .707;  --pd: .25s; }
.ic--sun .icp--4 { --dx: -1;    --dy: 0;     --pd: .3s;  }
.ic--sun .icp--8 { --dx: -.707; --dy: -.707; --pd: .35s; }
.ic--tee .icp--1 { --sx: 1; }  .ic--tee .icp--2 { --sx: -1; }
/* inventory arrows: top, right, bottom, left (clockwise), outward vectors */
.ic--arrows .icp--1 { --dx: 0;  --dy: -1; --pd: 0s;   }
.ic--arrows .icp--2 { --dx: 1;  --dy: 0;  --pd: .05s; }
.ic--arrows .icp--3 { --dx: 0;  --dy: 1;  --pd: .1s;  }
.ic--arrows .icp--4 { --dx: -1; --dy: 0;  --pd: .15s; }
.ic--frame .icp--2 { --s: 1; }  .ic--frame .icp--1 { --s: -1; } /* ⌜ / ⌟ corner */

/* Glance (the G) — a glance: a small lean the other way, a quick look to one
   side, a hold, a glance back past centre, and it settles */
@keyframes ic-g {
  0%        { transform: rotate(0deg);   animation-timing-function: cubic-bezier(.33, 0, .2, 1); }
  4%        { transform: rotate(3deg);   animation-timing-function: cubic-bezier(.16, 1, .3, 1); }
  13%, 22%  { transform: rotate(-20deg); animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  32%       { transform: rotate(9deg);   animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  40%       { transform: rotate(-3deg);  animation-timing-function: cubic-bezier(.33, 0, .2, 1); }
  46%, 100% { transform: rotate(0deg); }
}

/* Documents (crop) — the two brackets pull apart to open the crop, hold,
   snap back a touch too far and settle */
@keyframes ic-crop {
  0%        { transform: translate(0, 0); animation-timing-function: cubic-bezier(.33, 0, .2, 1); }
  6%        { transform: translate(calc(var(--sx) * -5 * var(--u)), calc(var(--sy) * -5 * var(--u))); animation-timing-function: cubic-bezier(.16, 1, .3, 1); }
  20%, 28%  { transform: translate(calc(var(--sx) * 30 * var(--u)), calc(var(--sy) * 30 * var(--u))); animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  38%       { transform: translate(calc(var(--sx) * -6 * var(--u)), calc(var(--sy) * -6 * var(--u))); animation-timing-function: cubic-bezier(.33, 0, .2, 1); }
  46%, 100% { transform: translate(0, 0); }
}

/* Custom fields (chevrons) — one smooth push forward and back, the back
   chevron a moment behind the front, so the pair stretches apart and closes up
   again. In-out easing throughout, no sudden reversals. */
@keyframes ic-chev {
  0%        { transform: translateX(0); animation-timing-function: cubic-bezier(.45, 0, .55, 1); }
  9%        { transform: translateX(calc(-6 * var(--u))); animation-timing-function: cubic-bezier(.45, 0, .2, 1); }
  26%       { transform: translateX(calc(40 * var(--u))); animation-timing-function: cubic-bezier(.45, 0, .55, 1); }
  30%       { transform: translateX(calc(40 * var(--u))); animation-timing-function: cubic-bezier(.45, 0, .25, 1); }
  48%, 100% { transform: translateX(0); }
}

/* Permissions — a gate: the chevrons open apart, the two bars slide in
   through the gap, then everything locks shut again */
@keyframes ic-x-v {
  0%        { transform: translateY(0); animation-timing-function: cubic-bezier(.33, 0, .2, 1); }
  6%        { transform: translateY(calc(var(--sy) * -6 * var(--u))); animation-timing-function: cubic-bezier(.16, 1, .3, 1); }
  17%, 30%  { transform: translateY(calc(var(--sy) * 34 * var(--u))); animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  40%       { transform: translateY(calc(var(--sy) * -7 * var(--u))); animation-timing-function: cubic-bezier(.33, 0, .2, 1); }
  48%, 100% { transform: translateY(0); }
}
@keyframes ic-x-h {
  0%, 10%   { transform: translateX(0); animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  21%, 24%  { transform: translateX(calc(var(--sx) * 46 * var(--u))); animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  31%       { transform: translateX(calc(var(--sx) * -5 * var(--u))); animation-timing-function: cubic-bezier(.33, 0, .2, 1); }
  38%, 100% { transform: translateX(0); }
}

/* Calendars (sun) — a ripple runs round the rays like a second hand, then the
   whole dial ticks one step (45°, which lands on itself) */
@keyframes ic-ray {
  0%        { transform: translate(0, 0); animation-timing-function: cubic-bezier(.16, 1, .3, 1); }
  7%        { transform: translate(calc(var(--dx) * 18 * var(--u)), calc(var(--dy) * 18 * var(--u))); animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  17%       { transform: translate(calc(var(--dx) * -4 * var(--u)), calc(var(--dy) * -4 * var(--u))); animation-timing-function: cubic-bezier(.33, 0, .2, 1); }
  23%, 100% { transform: translate(0, 0); }
}
@keyframes ic-sun {
  0%, 22%   { transform: rotate(0deg);  animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  34%       { transform: rotate(51deg); animation-timing-function: cubic-bezier(.33, 0, .2, 1); }
  40%, 100% { transform: rotate(45deg); }
}

/* Automations (tee) — a press: the bar lifts, drops, and the two legs are
   pushed apart by the landing, then everything springs back */
@keyframes ic-tee-bar {
  0%        { transform: translateY(0); animation-timing-function: cubic-bezier(.16, 1, .3, 1); }
  12%       { transform: translateY(calc(-34 * var(--u))); animation-timing-function: cubic-bezier(.55, 0, .9, .4); }
  19%       { transform: translateY(calc(10 * var(--u))); animation-timing-function: cubic-bezier(.16, 1, .3, 1); }
  27%       { transform: translateY(calc(-3 * var(--u))); animation-timing-function: cubic-bezier(.33, 0, .2, 1); }
  33%, 100% { transform: translateY(0); }
}
@keyframes ic-tee-leg {
  0%, 18%   { transform: translate(0, 0); animation-timing-function: cubic-bezier(.16, 1, .3, 1); }
  25%       { transform: translate(calc(var(--sx) * 24 * var(--u)), calc(6 * var(--u))); animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  34%       { transform: translate(calc(var(--sx) * -3 * var(--u)), 0); animation-timing-function: cubic-bezier(.33, 0, .2, 1); }
  40%, 100% { transform: translate(0, 0); }
}

/* Inventory (arrows) — the four arrows draw back, pack in towards the centre
   one after another, and the whole set turns a quarter (lands on itself) */
@keyframes ic-arrow {
  0%        { transform: translate(0, 0); animation-timing-function: cubic-bezier(.16, 1, .3, 1); }
  9%        { transform: translate(calc(var(--dx) * 20 * var(--u)), calc(var(--dy) * 20 * var(--u))); animation-timing-function: cubic-bezier(.55, 0, .9, .4); }
  17%       { transform: translate(calc(var(--dx) * -12 * var(--u)), calc(var(--dy) * -12 * var(--u))); animation-timing-function: cubic-bezier(.16, 1, .3, 1); }
  25%       { transform: translate(calc(var(--dx) * 3 * var(--u)), calc(var(--dy) * 3 * var(--u))); animation-timing-function: cubic-bezier(.33, 0, .2, 1); }
  30%, 100% { transform: translate(0, 0); }
}
@keyframes ic-arrows {
  0%, 26%   { transform: rotate(0deg);  animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  40%       { transform: rotate(96deg); animation-timing-function: cubic-bezier(.33, 0, .2, 1); }
  46%, 100% { transform: rotate(90deg); }
}

/* Files (frame) — the corners clamp the dot, the dot tucks in, the file
   turns over (180°, lands on itself), and the corners let go */
@keyframes ic-frame-c {
  0%        { transform: translate(0, 0); animation-timing-function: cubic-bezier(.33, 0, .2, 1); }
  6%        { transform: translate(calc(var(--s) * -6 * var(--u)), calc(var(--s) * -6 * var(--u))); animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  16%, 38%  { transform: translate(calc(var(--s) * 28 * var(--u)), calc(var(--s) * 28 * var(--u))); animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  48%       { transform: translate(calc(var(--s) * -5 * var(--u)), calc(var(--s) * -5 * var(--u))); animation-timing-function: cubic-bezier(.33, 0, .2, 1); }
  54%, 100% { transform: translate(0, 0); }
}
@keyframes ic-frame-dot {
  0%, 12%   { transform: scale(1);    animation-timing-function: cubic-bezier(.16, 1, .3, 1); }
  18%, 38%  { transform: scale(.7);   animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  47%       { transform: scale(1.16); animation-timing-function: cubic-bezier(.33, 0, .2, 1); }
  55%, 100% { transform: scale(1); }
}
@keyframes ic-frame {
  0%, 17%   { transform: rotate(0deg);   animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  33%       { transform: rotate(186deg); animation-timing-function: cubic-bezier(.33, 0, .2, 1); }
  37%, 100% { transform: rotate(180deg); }
}

/* Retainers & standing orders (roof) — the two legs draw in and the mark
   becomes a square with one rounded corner; it holds, and the legs grow back
   out. The mark is drawn as that square (.icp--1) plus its two legs (.icp--2/3),
   each leg the straight continuation of one side of the square, so a leg slides
   back along that side and disappears behind it. At rest it's the mark exactly
   (each leg runs 30 units on under the square, so there is no seam, and it
   never pulls clear of it when it overshoots). In both places
   the pieces are drawn at the pairs' size (the strip scales their group), so
   the slide is in plain units: --lx/--ly is each leg's full length. */
.ic--roof .icp--2 { --lx: 84.87px;  --ly: -85.25px; }
.ic--roof .icp--3 { --lx: -83.03px; --ly: -82.83px; --pd: .06s; }
@keyframes ic-leg {
  0%        { transform: translate(0, 0); animation-timing-function: cubic-bezier(.33, 0, .2, 1); }
  5%        { transform: translate(calc(var(--lx) * -.05), calc(var(--ly) * -.05)); animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  17%, 36%  { transform: translate(var(--lx), var(--ly)); animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  48%       { transform: translate(calc(var(--lx) * -.06), calc(var(--ly) * -.06)); animation-timing-function: cubic-bezier(.33, 0, .2, 1); }
  54%, 100% { transform: translate(0, 0); }
}

/* The three lockups (Glance, Yokey, Ching) play once, when they first come into
   view, and then stay as the finished logo. Their keyframes still describe a
   full 8s cycle, exit included, but each runs for .6 of an iteration: at 60%
   every part of all three is in its finished, holding state (after the wink /
   "ching", before anything fades or folds away), and fill-mode `both` keeps it
   there. A page load runs it again. */
/* ==========================================================================
   Glance lockup — the mark builds on its own in the middle of the band: the
   arc sweeps round from its left end, then the arrow (the G's crossbar and
   stem) shoots in along its own diagonal. The mark slides left into the
   lockup and the wordmark rises into place behind it.
   8s cycle: built by 2.4s, holds, winks once, folds away, 0.8s of bare grey.
   ========================================================================== */
.cell--glance { background: #222222; }
.glance-logo { width: 100%; height: 100%; }

/* Starts centred in the band at 1.35× and settles into the lockup.
   415.75 317.33 is the centre of the mark; +223.25px puts it on the band's
   centre line (639). The jump back at 100% happens while the mark is hidden. */
.gl-mark {
  transform-origin: 415.75px 317.33px;
  will-change: transform;
}
.cell--glance.is-in .gl-mark { animation: gl-settle 8s .6 both; }

@keyframes gl-settle {
  0%, 11.25% { transform: translateX(223.25px) scale(1.35); animation-timing-function: cubic-bezier(.76, 0, .24, 1); }
  19.4%, 100% { transform: none; }
}

/* A 40-wide stroke on the arc's centreline, used as a mask. Its butt end is
   square to the path, so the leading edge of the arc is always a radial cut.
   dasharray 1 1 on pathLength 1: offset 1 = nothing yet, 0 = whole arc,
   -1 = gone out of the far end. Both ends are empty, so the loop closes clean. */
.gl-sweep { stroke-dasharray: 1 1; stroke-dashoffset: 1; }
.cell--glance.is-in .gl-sweep { animation: gl-sweep 8s .6 both; }

@keyframes gl-sweep {
  0%, 2.5%  { stroke-dashoffset: 1; animation-timing-function: cubic-bezier(.5, 0, .1, 1); }
  10.6%     { stroke-dashoffset: 0; }
  82.5%     { stroke-dashoffset: 0; animation-timing-function: cubic-bezier(.6, 0, .4, 1); }
  90%, 100% { stroke-dashoffset: -1; }
}

/* The arrow arrives from inside the arc, travelling up and to the right —
   the direction it points — then gives one small nudge while the lockup holds. */
.gl-arrow { will-change: transform, opacity; }
.cell--glance.is-in .gl-arrow { animation: gl-arrow 8s .6 both; }

@keyframes gl-arrow {
  0%, 7.1%    { transform: translate(-22px, 22px); opacity: 0; animation-timing-function: cubic-bezier(.16, 1, .3, 1); }
  13.75%, 45% { transform: none; opacity: 1; animation-timing-function: ease-in-out; }
  47.5%       { transform: translate(3.5px, -3.5px); opacity: 1; animation-timing-function: ease-in-out; }
  50%, 81%    { transform: none; opacity: 1; animation-timing-function: cubic-bezier(.5, 0, .85, .25); }
  86.25%, 100% { transform: translate(-22px, 22px); opacity: 0; }
}

/* Each letter rises from under a clip set just below the g's descender,
   55ms apart, starting once the mark has nearly cleared the type, and sinks back the same way before the mark folds away. */
.gl-letter { transform: translateY(125px); will-change: transform; }
.cell--glance.is-in .gl-letter--1 { animation: gl-rise 8s 0s    .6 both; }
.cell--glance.is-in .gl-letter--2 { animation: gl-rise 8s .055s .6 both; }
.cell--glance.is-in .gl-letter--3 { animation: gl-rise 8s .11s  .6 both; }
.cell--glance.is-in .gl-letter--4 { animation: gl-rise 8s .165s .6 both; }
.cell--glance.is-in .gl-letter--5 { animation: gl-rise 8s .22s  .6 both; }
.cell--glance.is-in .gl-letter--6 { animation: gl-rise 8s .275s .6 both; }

@keyframes gl-rise {
  0%, 16.9%     { transform: translateY(125px); animation-timing-function: cubic-bezier(.16, 1, .3, 1); }
  26.25%, 75%      { transform: translateY(0); animation-timing-function: cubic-bezier(.55, 0, .85, .2); }
  80.5%, 100%   { transform: translateY(125px); }
}

/* ==========================================================================
   Ching lockup — the icon is the wordmark's C in a plate, its bowl broken
   into three rays: the "ching". It builds that way, alone in the middle of
   the band: the plate pops in, the C's two terminals slide in from the plate's
   right edge, and the rays shoot out of the centre one after another. The mark
   slides left into the lockup and each letter is wiped on by a capsule — the
   brand's pill. One more "ching" while it holds, then it fades out.
   8s cycle: built by 2.2s, fades at 6–6.5s, 1.5s of green before it rebuilds.
   ========================================================================== */
.cell--ching { background: #0E423E; }
.ching-logo { width: 100%; height: 100%; }

/* Everything fades out as one; every part resets while this is at zero. */
.ch-all { will-change: opacity; }
.cell--ching.is-in .ch-all { animation: ch-fade 8s .6 both; }
@keyframes ch-fade {
  0%, 75%       { opacity: 1; animation-timing-function: cubic-bezier(.45, 0, .55, 1); }
  81.25%, 99.9% { opacity: 0; }
  100%          { opacity: 1; }
}

/* Starts centred at 1.45×: 426.64 317.66 is the centre of the plate, +212.4px
   puts it on the band's centre line (639). */
.ch-mark { transform-origin: 426.64px 317.66px; will-change: transform; }
.cell--ching.is-in .ch-mark { animation: ch-settle 8s .6 both; }
@keyframes ch-settle {
  0%, 10.6%   { transform: translateX(212.4px) scale(1.45); animation-timing-function: cubic-bezier(.76, 0, .24, 1); }
  18.75%, 90% { transform: none; }
  90.1%, 100% { transform: translateX(212.4px) scale(1.45); }
}

/* The plate pops in, overshooting a little */
.ch-pop { transform-origin: 426.64px 317.66px; transform: scale(0); will-change: transform; }
.cell--ching.is-in .ch-pop { animation: ch-pop 8s .6 both; }
@keyframes ch-pop {
  0%, .6%     { transform: scale(0); animation-timing-function: cubic-bezier(.2, .9, .3, 1); }
  3.9%        { transform: scale(1.08); animation-timing-function: ease-in-out; }
  6.25%, 90%  { transform: none; }
  90.1%, 100% { transform: scale(0); }
}

/* The C's terminals slide in from the plate's right edge, clipped to the plate
   (62px puts them just outside it, so nothing shows before they move). */
.ch-bands { transform: translateX(62px); will-change: transform; }
.cell--ching.is-in .ch-bands { animation: ch-bands 8s .6 both; }
@keyframes ch-bands {
  0%, 3.1%    { transform: translateX(62px); animation-timing-function: cubic-bezier(.16, 1, .3, 1); }
  8.1%, 90%   { transform: none; }
  90.1%, 100% { transform: translateX(62px); }
}

/* The rays shoot out of the centre, each growing from its inner tip with an
   overshoot — lower, middle, upper, 90ms apart. */
.ch-ray { transform-box: fill-box; transform: scale(0); will-change: transform; }
.ch-ray--1 { transform-origin: 100% 0%; }    /* lower-left diagonal  */
.ch-ray--2 { transform-origin: 100% 50%; }   /* middle, horizontal   */
.ch-ray--3 { transform-origin: 100% 100%; }  /* upper-left diagonal  */
.cell--ching.is-in .ch-ray    { animation: ch-ray 8s .6 both; }
.cell--ching.is-in .ch-ray--1 { animation-delay: .47s; }
.cell--ching.is-in .ch-ray--2 { animation-delay: .56s; }
.cell--ching.is-in .ch-ray--3 { animation-delay: .65s; }
@keyframes ch-ray {
  0%          { transform: scale(0); animation-timing-function: cubic-bezier(.2, .9, .3, 1); }
  3.5%        { transform: scale(1.14); animation-timing-function: ease-in-out; }
  5%, 90%     { transform: none; }
  90.1%, 100% { transform: scale(0); }
}

/* One more "ching" while it holds: each ray kicks out along its own direction */
.ch-pulse { will-change: transform; }
.cell--ching.is-in .ch-pulse--1 { animation: ch-pulse-1 8s .6 both; }
.cell--ching.is-in .ch-pulse--2 { animation: ch-pulse-2 8s .6 both; }
.cell--ching.is-in .ch-pulse--3 { animation: ch-pulse-3 8s .6 both; }
@keyframes ch-pulse-1 { 0%, 28.1%, 33.1%, 100% { transform: none; } 30.6% { transform: translate(-3.7px, 3.7px); } }
@keyframes ch-pulse-2 { 0%, 28.1%, 33.1%, 100% { transform: none; } 30.6% { transform: translate(-5.2px, 0); } }
@keyframes ch-pulse-3 { 0%, 28.1%, 33.1%, 100% { transform: none; } 30.6% { transform: translate(-3.7px, -3.7px); } }

/* Typed: the letters appear one at a time, each all at once, like keys being
   hit — no caret, no motion — at a slightly uneven rhythm (120, 90, 150, 90ms).
   They start once the mark has nearly slid clear of the type. */
.ch-l { opacity: 0; }
.cell--ching.is-in .ch-l    { animation: ch-type 8s .6 both; }
.cell--ching.is-in .ch-l--2 { animation-delay: .12s; }
.cell--ching.is-in .ch-l--3 { animation-delay: .21s; }
.cell--ching.is-in .ch-l--4 { animation-delay: .36s; }
.cell--ching.is-in .ch-l--5 { animation-delay: .45s; }
@keyframes ch-type {
  0%, 17.49%  { opacity: 0; }
  17.5%, 90%  { opacity: 1; }
  90.1%, 100% { opacity: 0; }   /* reset, already faded out */
}

/* The pattern's CHING wordmark still ripples on this one (see below). */
@keyframes ching-lift {
  0%, 16%, 100% { transform: translateY(0) scaleY(1); }
  6%            { transform: translateY(-11%) scaleY(1.05); }
  12%           { transform: translateY(2%) scaleY(.98); }
}

/* ==========================================================================
   Ching capsule pattern
   Rules of the composition: every row runs 56 → 578 and must keep doing so.
   So capsules are only ever animated in pairs that share an edge — one grows
   by exactly what the other gives up, and the outer margins never move.
   ========================================================================== */
.cell--pattern { background: #FFFFFF; }
.cp { width: 100%; height: 100%; }

.cap, .cpm, .cp-letter, .cp-arrow, .cp-caret, .cp-type-mask {
  transform-box: fill-box;
  will-change: transform;
}

/* --- capsules: three pairs, 5px traded back and forth, staggered so only one
       pair is ever really in motion ------------------------------------------ */
.cap--6, .cap--5, .cap--14 { transform-origin: 0% 50%; }     /* grow rightwards */
.cap--1, .cap--10, .cap--13 { transform-origin: 100% 50%; }  /* give way        */

/* widths: 6=408.3  1=113.8 | 5=113.8  10=180.6 | 14=113.8  13=113.8            */
.cell--pattern.is-in .cap--6  { animation: cap-grow-6  7s ease-in-out infinite both; }
.cell--pattern.is-in .cap--1  { animation: cap-give-1  7s ease-in-out infinite both; }
.cell--pattern.is-in .cap--5  { animation: cap-grow-5  7s ease-in-out 1.2s infinite both; }
.cell--pattern.is-in .cap--10 { animation: cap-give-10 7s ease-in-out 1.2s infinite both; }
.cell--pattern.is-in .cap--14 { animation: cap-grow-14 7s ease-in-out 2.6s infinite both; }
.cell--pattern.is-in .cap--13 { animation: cap-give-13 7s ease-in-out 2.6s infinite both; }

@keyframes cap-grow-6  { 0%, 62%, 100% { transform: scaleX(1); } 28% { transform: scaleX(1.0122); } }
@keyframes cap-give-1  { 0%, 62%, 100% { transform: scaleX(1); } 28% { transform: scaleX(0.9561); } }
@keyframes cap-grow-5  { 0%, 62%, 100% { transform: scaleX(1); } 28% { transform: scaleX(1.0439); } }
@keyframes cap-give-10 { 0%, 62%, 100% { transform: scaleX(1); } 28% { transform: scaleX(0.9723); } }
@keyframes cap-grow-14 { 0%, 62%, 100% { transform: scaleX(1); } 28% { transform: scaleX(1.0439); } }
@keyframes cap-give-13 { 0%, 62%, 100% { transform: scaleX(1); } 28% { transform: scaleX(0.9561); } }

/* --- arrow: a small nudge along its own line ------------------------------- */
.cp-arrow { transform-origin: 50% 50%; }
.cell--pattern.is-in .cp-arrow { animation: cp-nudge 3.4s ease-in-out infinite both; }
@keyframes cp-nudge {
  0%, 55%, 100% { transform: translateX(0); }
  30%           { transform: translateX(7px); }
}

/* --- the mark: the plate stays put; only the white icon on it moves, on an
       8s loop. It builds as the lockup's does — the C's terminals slide in
       from the plate's edge (clipped to it), the rays shoot out of the centre
       one after another and give one "ching" — holds, then takes itself back
       apart: the rays pull back into the centre in the same order, the
       terminals slide back out, and the bare plate rests before it rebuilds.
       Distances scaled from the lockup's ×2.3834 to this copy's ×2.2157. ---- */

.cp-bands { transform: translateX(58px); will-change: transform; }   /* just outside the plate */
.cell--pattern.is-in .cp-bands { animation: cp-bands 8s infinite both; }
@keyframes cp-bands {
  0%, 3.1%    { transform: translateX(58px); animation-timing-function: cubic-bezier(.16, 1, .3, 1); }
  8.1%, 90%   { transform: none; animation-timing-function: cubic-bezier(.55, 0, .9, .3); }
  95%, 100%   { transform: translateX(58px); }   /* back out once the last ray is in */
}

.cpm--2 { transform-origin: 100% 0%; }    /* lower-left diagonal */
.cpm--3 { transform-origin: 100% 50%; }   /* mid-left horizontal */
.cpm--5 { transform-origin: 100% 100%; }  /* upper-left diagonal */
.cpm--2, .cpm--3, .cpm--5 { transform: scale(0); }
.cell--pattern.is-in .cpm--2 { animation: cp-ray 8s .47s infinite both; }
.cell--pattern.is-in .cpm--3 { animation: cp-ray 8s .56s infinite both; }
.cell--pattern.is-in .cpm--5 { animation: cp-ray 8s .65s infinite both; }
/* the lockup's ray, plus a way back: an ease-in retract into the inner tip */
@keyframes cp-ray {
  0%          { transform: scale(0); animation-timing-function: cubic-bezier(.2, .9, .3, 1); }
  3.5%        { transform: scale(1.14); animation-timing-function: ease-in-out; }
  5%, 80%     { transform: none; animation-timing-function: cubic-bezier(.5, 0, .85, .25); }
  85%, 100%   { transform: scale(0); }
}

.cp-orbit { will-change: transform; }     /* each ray's wrapper carries its "ching" */
.cell--pattern.is-in .cp-orbit--1 { animation: cp-pulse-1 8s infinite both; }
.cell--pattern.is-in .cp-orbit--2 { animation: cp-pulse-2 8s infinite both; }
.cell--pattern.is-in .cp-orbit--3 { animation: cp-pulse-3 8s infinite both; }
@keyframes cp-pulse-1 { 0%, 28.1%, 33.1%, 100% { transform: none; } 30.6% { transform: translate(-3.44px, 3.44px); } }
@keyframes cp-pulse-2 { 0%, 28.1%, 33.1%, 100% { transform: none; } 30.6% { transform: translate(-4.83px, 0); } }
@keyframes cp-pulse-3 { 0%, 28.1%, 33.1%, 100% { transform: none; } 30.6% { transform: translate(-3.44px, -3.44px); } }

/* --- CHING: the same ripple as the lockup ---------------------------------- */
.cp-letter { transform-origin: 50% 50%; }
.cell--pattern.is-in .cp-letter--1 { animation: ching-lift 4.4s ease-in-out 1.57s infinite both; }
.cell--pattern.is-in .cp-letter--2 { animation: ching-lift 4.4s ease-in-out 1.64s infinite both; }
.cell--pattern.is-in .cp-letter--3 { animation: ching-lift 4.4s ease-in-out 1.71s infinite both; }
.cell--pattern.is-in .cp-letter--4 { animation: ching-lift 4.4s ease-in-out 1.78s infinite both; }
.cell--pattern.is-in .cp-letter--5 { animation: ching-lift 4.4s ease-in-out 1.85s infinite both; }

/* --- Billing: typed in, held, then backspaced out -------------------------- */
/* "Billing" is a single path, so the letters are revealed by widening a clip
   in seven discrete steps rather than by animating seven separate glyphs. */
.cp-type-mask { transform-origin: 0% 50%; }
.cell--pattern.is-in .cp-type-mask { animation: cp-type 6.4s infinite both; }
.cell--pattern.is-in .cp-caret {
  animation: cp-caret 6.4s infinite both,
             cp-blink 1s steps(2, jump-none) infinite;
}
@keyframes cp-type {
  0%        { transform: scaleX(0); animation-timing-function: steps(7, end); }
  25%       { transform: scaleX(1); animation-timing-function: linear; }
  72%       { transform: scaleX(1); animation-timing-function: steps(7, end); }
  87%, 100% { transform: scaleX(0); }
}

/* The caret rides the end of the word, one letter-width at a time */
@keyframes cp-caret {
  0%        { transform: translateX(0); animation-timing-function: steps(7, end); }
  25%       { transform: translateX(124.7px); animation-timing-function: linear; }
  72%       { transform: translateX(124.7px); animation-timing-function: steps(7, end); }
  87%, 100% { transform: translateX(0); }
}

@keyframes cp-blink { 0% { opacity: 1; } 100% { opacity: 0; } }

/* ==========================================================================
   Construction grids (Yokey, Glance) — drawn by assets/js/construction-grids.js
   onto a canvas laid over the static artwork. The <img> stays underneath as
   the fallback: the canvas only shows once the script has taken over the cell.
   ========================================================================== */
.grid-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: none; }
.is-live > .grid-canvas { display: block; }

/* ==========================================================================
   Yokey lockup — built like something typed on a keyboard, in place.
   The icon arrives as three keys, each dropping onto the band with a small
   squash; the neck that welds the top two grows in; then the wordmark is typed
   one letter at a time, at a slightly uneven rhythm. It holds (the top key is
   pressed once), then the whole lockup fades out together.
   8s cycle, about 1.5s of black before it starts again.
   ========================================================================== */
.cell--yokey { background: #000; }
.yk-logo { width: 100%; height: 100%; }

/* Keys and letters both scale from their own bottom edge — a key lands on
   it, a letter is typed up from its base. */
.yk-key, .yk-letter {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  will-change: transform, opacity;
}

/* Drop: each key falls about a key and a half, lands, squashes and springs
   back — middle, top, bottom, 120ms apart. It resets to its starting place
   only once the fade below has taken the lockup out. */
.yk-key { opacity: 0; }
.cell--yokey.is-in .yk-key    { animation: yk-key 8s .6 both; }
.cell--yokey.is-in .yk-key--m { animation-delay: .10s; }
.cell--yokey.is-in .yk-key--u { animation-delay: .22s; }
.cell--yokey.is-in .yk-key--b { animation-delay: .34s; }

@keyframes yk-key {
  0%          { transform: translateY(-51px); opacity: 0; animation-timing-function: cubic-bezier(.16, 1, .3, 1); }
  1.75%       { transform: translateY(-4px); opacity: 1; animation-timing-function: ease-in-out; }
  2.8%        { transform: scaleY(.78); opacity: 1; animation-timing-function: ease-in-out; }   /* lands */
  3.9%, 90%   { transform: none; opacity: 1; }
  90.1%, 100% { transform: translateY(-51px); opacity: 0; }   /* reset, already faded out */
}

/* The neck: two concave fillets, grown from the corner where the keys touch.
   Scaling a fillet about that corner gives a true fillet of the smaller radius
   at every frame, so the two keys read as melting together. */
.yk-neck {
  transform-origin: 423.805px 295.277px;
  transform: scale(0);
  will-change: transform;
}
.cell--yokey.is-in .yk-neck { animation: yk-neck 8s .6 both; }

@keyframes yk-neck {
  0%, 8.25%   { transform: scale(0); animation-timing-function: cubic-bezier(.16, 1, .3, 1); }
  11.5%, 90% { transform: scale(1); }
  90.1%, 100% { transform: scale(0); }   /* reset, already faded out */
}

/* While the lockup holds, the top key is pressed once — fast down, soft return */
.yk-wink { will-change: transform; }
.cell--yokey.is-in .yk-wink { animation: yk-wink 8s .22s .6 both; }

@keyframes yk-wink {
  0%, 30%    { transform: none; animation-timing-function: cubic-bezier(.3, 0, .7, 1); }
  30.75%     { transform: translateY(2.6px); animation-timing-function: cubic-bezier(.3, 0, .3, 1); }
  32.75%     { transform: translateY(-.5px); animation-timing-function: ease-in-out; }
  34.75%, 100% { transform: none; }
}

/* Typed: each letter pops up from its base with a little overshoot. The gaps
   are deliberately uneven (110, 80, 140, 80ms) — the rhythm of real typing,
   not a metronome. */
.yk-letter { opacity: 0; }
.cell--yokey.is-in .yk-letter { animation: yk-type 8s .6 both; }
.cell--yokey.is-in .yk-letter--1 { animation-delay: 0s; }
.cell--yokey.is-in .yk-letter--2 { animation-delay: .11s; }
.cell--yokey.is-in .yk-letter--3 { animation-delay: .19s; }
.cell--yokey.is-in .yk-letter--4 { animation-delay: .33s; }
.cell--yokey.is-in .yk-letter--5 { animation-delay: .41s; }

@keyframes yk-type {
  0%, 13.1%   { transform: scale(.7, 0); opacity: 0; animation-timing-function: cubic-bezier(.3, 1.7, .5, 1); }
  15.9%, 90%  { transform: none; opacity: 1; }
  90.1%, 100% { transform: scale(.7, 0); opacity: 0; }   /* reset, already faded out */
}

/* Out: the whole lockup fades as one, so nothing comes apart on the way out.
   Everything inside resets while this group is at zero. */
.yk-all { will-change: opacity; }
.cell--yokey.is-in .yk-all { animation: yk-fade 8s .6 both; }

@keyframes yk-fade {
  0%, 75%   { opacity: 1; animation-timing-function: cubic-bezier(.45, 0, .55, 1); }
  81.25%, 99.9% { opacity: 0; }
  100%      { opacity: 1; }
}

/* ==========================================================================
   Reveal on scroll
   ========================================================================== */
.reveal { opacity: 0; transform: translateY(24px); }
.reveal.is-in { opacity: 1; transform: none; transition: opacity .7s ease, transform .7s cubic-bezier(.22,.61,.36,1); }

/* The logo lockups play once, so fading in is not enough to start them: on a
   phone the band shows up at the bottom of the screen and would be done by the
   time it is scrolled to. They hold on their first frame until main.js says
   the band has reached the middle of the screen. */
.cell--yokey:not(.is-playing) *,
.cell--ching:not(.is-playing) *,
.cell--glance:not(.is-playing) * { animation-play-state: paused !important; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .reveal, .reveal.is-in { opacity: 1; transform: none; transition: none; }
  * { animation-duration: .001ms !important; transition-duration: .001ms !important; }

  /* Collapsing the duration freezes an animation on its last keyframe, and
     several of these end on nothing at all — a dash at scale 0, an unlit sign,
     an untyped word. Switch them off outright and show the artwork at rest. */
  .ic, .icp,
  .ch-all, .ch-mark, .ch-pop, .ch-bands, .ch-ray, .ch-pulse, .ch-l,
  .cap, .cpm, .cp-orbit, .cp-bands, .cp-letter, .cp-arrow, .cp-type-mask,
  .yk-all, .yk-key, .yk-wink, .yk-letter, .gpair__art,
  .gl-mark, .gl-arrow, .gl-letter {
    animation: none !important;
    transform: none !important;
    opacity: 1 !important;
    filter: none !important;
  }
  /* nothing is being typed, so the caret has no business being there */
  .cp-caret { animation: none !important; opacity: 0 !important; }
  /* the arc's mask would otherwise sit at its empty starting offset */
  .gl-sweep { animation: none !important; stroke-dashoffset: 0 !important; }
  /* the neck starts at scale 0 */
  .yk-neck { animation: none !important; transform: none !important; }
}

/* ==========================================================================
   Responsive
   ========================================================================== */

/* --- About stacks once the portrait is too short to carry the text column,
       i.e. once the button could no longer sit on the portrait's bottom edge --- */
@media (max-width: 1279px) {
  .about {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 48px;
    padding-top: 64px;
  }
  /* The paragraphs are hand-broken, so they do not fill the column — sizing the
     block to its widest line keeps the button flush with the text rather than
     running past it. It still gives way to the viewport on a narrow screen. */
  .about__body {
    gap: 64px;
    width: fit-content;
    max-width: 100%;
    padding-inline: var(--pad-x);
  }
  .about__body .btn { max-width: none; }
  .about__blocks { margin-top: 0; }
  .about__portrait { aspect-ratio: 4 / 3; }
}

/* --- Tablet: the project grid keeps its shape, the intro stacks --- */
@media (max-width: 1023px) {
  .intro {
    flex-direction: column;
    align-items: flex-start;
    gap: 24px;
    padding: 64px var(--pad-x) 32px;
  }
  .intro__id { flex: 0 0 auto; width: 100%; }
  .intro__text { flex: 0 0 auto; width: 100%; text-align: start; text-wrap: initial; }
  .intro--home .intro__text p { margin-inline-start: 0; }
}

/* --- Small tablet / large phone --- */
@media (max-width: 767px) {
  :root { --pad-x: 20px; }

  .cell--posters { aspect-ratio: auto !important; }
  .cell--posters .posters__wide { display: none; }
  .posters__grid {
    display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
    padding: 32px var(--pad-x);
  }
  .posters__grid img { width: 100%; height: auto; }

  /* Website bands: more room above and below the screenshot
     (150 of 1920 each side; padding % is of the band's width) */
  .cell--web { aspect-ratio: auto !important; padding-block: calc(150 / 1920 * 100%); }

  /* Glance module pairs: each colour half a little taller than square */
  .cell--gpairs.ar-full { aspect-ratio: 8 / 5; }

  /* Glance module names: twice the size so they stay readable */
  .gpair__name { transform: scale(2); }

  /* Everything becomes a single column */
  .row--2,
  .row--3 { grid-template-columns: 1fr; }

  /* the links to the next and previous projects keep to the page's own margins */
  .pnav { padding-inline: var(--pad-x); }
  .cell--span2 { grid-column: auto; }

  /* The three full-logo cells go square once stacked, and the logo inside grows
     a little: the SVG keeps its 1278×635 band centred in the square (the cell
     is filled with the same background colour) and is scaled up by 1.3 — the
     cell's overflow is hidden, and each lockup still clears the edges. */
  .ar-band { aspect-ratio: 1 / 1; }
  .ar-band > svg { transform: scale(1.3); }

  /* The icon strip becomes a square with the nine marks three to a row.
     Each mark sits in a wrapper (.gip) that only moves on mobile, so its own
     idle animation keeps running untouched inside it. The SVG keeps its
     1920×400 viewBox; in a square cell it shows 1920 units top to bottom
     (y −760…1160), so the rows go at y −440, 200 and 840 and the columns at
     x 320, 960 and 1600, each mark scaled up 2.3× about its own centre. */
  .ar-strip { aspect-ratio: 1 / 1; }

  .gip { transform-box: view-box; }
  .gip--1 { transform-origin: 142.93px 199.7px; transform: translate(177.07px, -639.70px) scale(2.3); }
  .gip--2 { transform-origin: 350.33px 199.75px; transform: translate(609.67px, -639.75px) scale(2.3); }
  .gip--3 { transform-origin: 549.12px 199.7px; transform: translate(1050.88px, -639.70px) scale(2.3); }
  .gip--4 { transform-origin: 748.5px 199.75px; transform: translate(-428.50px, 0.25px) scale(2.3); }
  .gip--5 { transform-origin: 956.61px 199.75px; transform: translate(3.39px, 0.25px) scale(2.3); }
  .gip--6 { transform-origin: 1163.64px 199.7px; transform: translate(436.36px, 0.30px) scale(2.3); }
  .gip--7 { transform-origin: 1366.06px 199.75px; transform: translate(-1046.06px, 640.25px) scale(2.3); }
  .gip--8 { transform-origin: 1560.22px 199.75px; transform: translate(-600.22px, 640.25px) scale(2.3); }
  .gip--9 { transform-origin: 1768.34px 199.75px; transform: translate(-168.34px, 640.25px) scale(2.3); }
  .ar-full { aspect-ratio: 4 / 3; }

  /* The billboard is a wide piece of artwork, and a 4:3 box bites into its
     edges — the logo on one side, the "Before" label on the other. The photo
     carries wall to spare on either side of the panel, and 16:10 crops into
     that instead of into the work. */
  .cell--billboard { aspect-ratio: 16 / 10; }

  /* Header becomes a burger menu */
  .nav-toggle { display: grid; }

  .site-nav {
    order: 1;
    flex: 0 0 100%;
    flex-direction: column;
    align-items: flex-start;
    gap: 48px;
    padding: 0;                 /* padding would not collapse with the height */
    height: 0;
    overflow: hidden;
    transition: height .4s cubic-bezier(.22,.61,.36,1);
  }
  /* breathing room under the last item, as a flex item so it counts in the
     height the script measures */
  .site-nav::after { content: ""; display: block; height: 4px; flex: none; }
  .site-nav > :first-child { margin-top: 44px; }

  .site-nav .nav-link { font-size: 16px; font-weight: 500; }

  /* There's no hover in the overlay, so both languages sit under the item */
  .site-nav .cv-menu:hover .nav-link::after { transform: scaleX(0); }
  .site-nav .cv-menu__list {
    position: static;
    flex-direction: row;
    gap: 12px;
    min-width: 0;
    margin-top: 16px;
    background: none;
    border: 0;
    opacity: 1;
    visibility: visible;
    transform: none;
  }
  .site-nav .cv-menu__list::before { display: none; }
  /* On a phone the two languages are buttons, cut like the language switch in
     the header: square, a hairline of the text's own colour, filling on press. */
  .site-nav .cv-menu__item {
    gap: 8px;
    padding: 9px 14px;
    font-size: var(--t-16);
    text-decoration: none;
    border: 1px solid currentColor;
    transition: background .25s ease, color .25s ease;
  }
  .site-nav .cv-menu__item:hover,
  .site-nav .cv-menu__item:focus-visible,
  .site-nav .cv-menu__item:active { background: var(--fg); color: var(--bg); outline: none; }
  .site-nav .cv-menu__item .icon-download { width: 17px; height: 17px; }

  /* Sticky on phones only — otherwise the menu is unreachable mid-page */
  .site-header {
    position: sticky;
    top: 0;
    z-index: 100;
    padding: 20px var(--pad-x);
  }

  .intro { padding: 48px var(--pad-x) 28px; }

  /* About */
  .about__portrait { aspect-ratio: 1 / 1; }
  .about__body { gap: 48px; }
  .about__blocks { gap: 36px; }

  /* Footer */
  .site-footer { padding-top: 64px; gap: 36px; }
  .footer__cols { flex-direction: column; gap: 36px; }
}

@media (max-width: 400px) {
  .footer__cols { gap: 32px; }
}

/* ==========================================================================
   Hebrew

   The same pages, read the other way. There is one set of files, not two: the
   Hebrew copy rides along on data-he attributes next to the English, and
   assets/js/lang.js swaps it in and turns the document around. Almost nothing
   here needs mirroring by hand — the layout is flex and grid, which turns
   around on its own — so this block is only the handful of places that don't.
   ========================================================================== */

/* General Sans has no Hebrew glyphs, so the Hebrew face takes over wholesale.
   Rubik carries both alphabets, which keeps the wordmark and the Latin brand
   names in the same voice as the rest. */
:root[lang="he"] body {
  font-family: 'Google Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
  letter-spacing: 0;
  /* Hebrew has no lower case to lighten a line, so the medium the Latin text is
     set in comes out heavy here. Everything that is 500 drops to regular; the
     names and the wordmark keep their weight. */
  font-weight: 400;
}
:root[lang="he"] .t-role,
:root[lang="he"] .t-body,
:root[lang="he"] .t-link { font-weight: 400; }

/* The switch, in the header next to the nav. The wrapper has to take the
   header's spare width, or .site-nav's 816px basis collapses to the width of
   its three links and they bunch up against each other. */
.header__end {
  display: flex; align-items: center; justify-content: flex-end;
  flex: 1 1 auto; min-width: 0; gap: 32px;
}

.lang-toggle {
  font: inherit;
  /* a notch under --t-16, in the same clamp idiom as the rest of the scale */
  font-size: clamp(11px, 0.68vw, 13px);
  color: inherit;
  white-space: nowrap;
  letter-spacing: .04em;
  cursor: pointer;
  padding: 4px 8px;
  border: 1px solid currentColor;
  background: none;
  transition: background .25s ease, color .25s ease;
}
.lang-toggle:hover { background: var(--fg); color: var(--bg); }

/* The underline grows from the side the text starts on. */
[dir="rtl"] .nav-link::after { transform-origin: right; }

/* The project grids are compositions, not prose. Mirroring them would move the
   artwork to the other side of the page, so they stay as they were drawn —
   only the writing around them turns around. */
[dir="rtl"] .work { direction: ltr; }

/* Previous and next swap sides, so the arrows have to point the other way. The
   nudge on hover is written through the flip, hence the doubled transform. */
[dir="rtl"] .pnav__arrow { transform: scaleX(-1); }
[dir="rtl"] .pnav__link--prev:hover .pnav__arrow,
[dir="rtl"] .pnav__link--prev:focus-visible .pnav__arrow { transform: scaleX(-1) translateX(-4px); }
[dir="rtl"] .pnav__link--next:hover .pnav__arrow,
[dir="rtl"] .pnav__link--next:focus-visible .pnav__arrow { transform: scaleX(-1) translateX(4px); }

@media (max-width: 767px) {
  /* The menu opens inside the header and pushes the page down rather than
     covering it: .header__end stops being a box of its own, so the nav can wrap
     onto a line of its own underneath at full width. These rules live here, at
     the end, because the base .header__end above them would otherwise win on
     source order alone. */
  .header__end { display: contents; }

  .site-header {
    flex-wrap: wrap;
    justify-content: flex-start;
    column-gap: 16px;
    row-gap: 0;
  }
  .site-header > .t-wordmark { margin-inline-end: auto; }

  .lang-toggle { position: relative; }
}
