/* ---------------------------------------------------------------
   Jitterbug Folk — motion study
   Geometry, type and colour are taken verbatim from the Figma
   frames "1" (138:36) and "2" (139:55), both 1512 x 982 on white.
   The only thing added here is time.
---------------------------------------------------------------- */

:root{
  /* motion language — retune everything from here */
  --ease-entry:  cubic-bezier(0.22, 1, 0.36, 1);
  --ease-cross:  cubic-bezier(0.4, 0.0, 0.2, 1);
  --dur-glyph:   1200ms;
  --dur-glyph-2: 1620ms;     /* board 2's lockup arrives 35% slower than board 1's */
  --dur-fade:    900ms;
  --stagger-line: 260ms;

  /* the phrase cycler runs on its own, slower clock */
  --dur-cycle:   1130ms;   /* board 2: one row travelling one step (15% faster) */
  --row-h:       26px;     /* one row of the stack, from the frame's 26px leading */
  --stack-bleed: 26px;     /* one row of soft margin above and below the window */

  /* board 1 — letters leave and arrive one at a time, never overlapping */
  --dur-letter:      700ms;  /* one letter's exit, or its entry */
  --stagger-letter:  40ms;   /* between consecutive letters, left to right */
  --letter-rise:     28px;   /* how far a letter travels as it leaves */
  --letter-dip:      4px;    /* the anticipation dip before it rises */
  --cycle-in-from:   -28px;  /* where an arriving letter starts: negative = from above */
  --cycle-gap:       150ms;  /* the beat between the last exit and the first entry */
  --ease-letter-out: cubic-bezier(0.3, 0.0, 0.2, 1);        /* the rise, after the dip */
  --ease-letter-dip: cubic-bezier(0.4, 0.0, 0.6, 1);        /* into the dip */
  --ease-letter-in:  cubic-bezier(0.34, 1.35, 0.64, 1);     /* settles past, then back */

  /* the headline reveal — phrased by word and line rather than ticking
     through characters at a fixed rate */
  /* the headline arrives a word at a time, fading only — no movement */
  --headline-dur:   1400ms;  /* one word fading up */
  --headline-step:  180ms;   /* between words — short against the duration, so
                                about eight are always mid-fade and the reveal
                                is a wash rather than a row of separate events */

  /* when the mascot sets off, measured back from the end of the headline.
     Anchoring the START (rather than deriving it from the walk's length) means
     stretching the walk's tail lengthens its ending instead of shifting its
     beginning — which is the point of the settle below. */
  --walk-start-before: 5006ms;
  --walk-settle:       0.35;  /* the last third of the frames stretch out */
  --walk-settle-max:   3;     /* the final frame runs this much longer */
  --walk-fade:      1600ms;  /* it crossfades in as it enters, rather than
                                appearing at full strength at the right edge */

  /* phone */
  --m-ticker-gap:   22px;    /* between entries in the sliding ticker */
  --m-ticker-speed: 32;      /* board px per second, left */
  --m-grey:         #777;    /* the frame's ticker grey */
  --m-ticker-fade:  56px;    /* entries dissolve over this much at each end */

  /* board 2 — the ramp a row climbs as it travels to the centre */
  --ease-throw:      cubic-bezier(0.34, 1.4, 0.64, 1);
  --grey-ramp:       #6E6E6E;  /* denser than the frame's flat grey */
  --row-opacity-in:  0.55;     /* entering, below the centre: the weakest state */
  --row-opacity-out: 0.75;     /* leaving, above the centre */
  --row-wght-in:     500;    /* the arriving row now carries the leaving row's
                                weight, so the two grey rows match */
  --row-wght-out:    500;
  --row-wght-on:     800;    /* Alegreya ExtraBold at the centre */

  /* frame palette, read from Figma */
  --ink:    #000000;
  --grey:   #979797;
  --paper:  #FFFFFF;
  /* the letterforms' colour. The j/b/f SVGs are filled with currentColor and
     inlined, so this token is the only place the colour is written down. */
  --letter: #371117;

/* board 2 follows its own frame: cream paper, a deeper red for the
   letterforms and a red email. Board 1 keeps the values above. */
}
body.board-2{
  --paper:  #FFFAEB;
  --letter: #610809;
  --accent: #E2000E;

  --frame-w: 1512px;
  --frame-h: 982px;

}

*{ box-sizing: border-box; }

html, body{
  margin:0; padding:0; height:100%;
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

.viewport{
  position:fixed; inset:0;
  display:flex; align-items:center; justify-content:center;
  overflow:hidden;
}

.stage{
  position:relative;
  width: var(--frame-w);
  height: var(--frame-h);
  background: var(--paper);
  transform-origin: center center;
  flex: 0 0 auto;
}

/* every board element is absolutely placed at its Figma x/y/w/h */
.node{ position:absolute; }
.node img{ display:block; width:100%; height:100%; }
/* inlined letterforms: the SVG fill is currentColor, so the token drives it */
.node.letter{ color: var(--letter); }
.node.letter svg{ display:block; width:100%; height:100%; }

/* ---------- typography (Figma: Instrument Sans + Jeju Hallasan) ---------- */
.t{
  font-family:'Instrument Sans', system-ui, sans-serif;
  font-variation-settings:"wdth" 100;
  font-weight:500;
  font-size:17px;
  line-height:22px;
  color: var(--ink);
  margin:0;
  word-break: break-word;
}
.t.lead{ font-size:47px; line-height:55px; }
.t b{ font-weight:700; font-variation-settings:"wdth" 100; }
/* The address is a live mailto link. The boards are pixel-faithful at rest, so
   it has to look exactly as it did — the affordance is hover and focus only.
   The underline sits on .w as well as the anchor because splitWords wraps every
   word in an inline-block, and text decoration is not painted across an atomic
   inline box: on the <a> alone it would silently render nothing. Declared above
   .email so that rule still wins the colour on board 2 and phone. */
.t .mail{ color: inherit; text-decoration: none; cursor: pointer; }
.t .mail:hover, .t .mail:hover .w{
  text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px;
}
.t .mail:focus-visible{ outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; }
.t .email{ color: var(--accent); font-weight:700; font-variation-settings:"wdth" 100; }
.t .brand{ color: var(--accent); font-weight:700; font-variation-settings:"wdth" 100; }
.t.lg{ font-size:19px; line-height:26px; }
.t.loose{ line-height:26px; }

/* phone type, from frame 161:314 */
/* left-aligned: only the contact block below is centred */
.t.m-head{ font-size:20px; line-height:28px; }
.t.m-body{ font-size:14px; line-height:22px; }
.t.m-contact{ font-size:14px; line-height:23px; text-align:center; white-space:nowrap; }
/* The address is 15px tall, far under a thumb's worth, so on the phone the
   anchor carries an invisible hit area about 45px deep. It is a positioned
   pseudo-element rather than padding, so nothing in the layout shifts. The
   growth is biased downward — the contact block is the last thing on the
   page, so that space is free, while upward it would start stealing taps
   from the line above. */
.t.m-contact .mail{ position: relative; }
.t.m-contact .mail::after{
  content:''; position:absolute; left:-10px; right:-10px; top:-6px; bottom:-24px;
}


/* DISPLAY FACE ---------------------------------------------------
   The frame sets this phrase in JejuHallasan, which is not
   installed here and is no longer served by Google (its
   early-access binary 404s). Kadwa is used instead, by request. */
.brush{
  font-family:'Kadwa', serif;
  font-weight:400;
  font-size:73px;
  line-height:62px;
  text-align:center;
  color: var(--ink);
  margin:0;
  white-space: pre-wrap;
}

/* ---------- entrance states ---------- */
/* Both frames sit on pure white, so "fade up from white" is an
   opacity fade — identical result, no per-span colour interpolation. */
.anim{ opacity:0; }
/* no will-change: board 2 has 73 of these, and pinning every one to its own
   compositing layer for the whole session is what made the moment they all
   start together hitch. Short WAAPI animations are promoted on their own. */
.w{ display:inline-block; opacity:0; }
/* no will-change here on purpose: it would promote every one of the 125
   character spans to its own compositing layer */
.ch{ opacity:0; }

/* ---------- word cycler: board 1 (one phrase at a time) ----------
   No overflow clipping here. Kadwa's glyph box is far taller than the
   frame's 62px leading, so any box edge slices the descenders — and a
   phrase that fades while half-cut reads as a bar, not a crossfade.
   The travel is small enough that it needs no window.              */
.cycler-single{ overflow:visible; }
.cycler-single .phrase{
  position:absolute; inset:0;
  display:flex; align-items:flex-start; justify-content:center;
}
.cycler-single .phrase-text{ display:block; width:100%; }
/* scoped to this cycler on purpose: the typewriter's .ch spans must stay
   inline, or inline-block boxes would let the headline break mid-word */
.cycler-single .ch{
  display:inline-block;
  will-change:opacity, transform;
}

/* ---------- word cycler: board 2 (rolling three-line stack) ----------
   This one is a window by design - the frame shows exactly three lines.
   The three design rows sit fully opaque; the rows arriving and leaving
   fade across one row of bleed, so nothing is ever cut by a hard edge. */
.cycler-stack{ overflow:hidden; }
.cycler-stack .track{
  position:absolute; left:0; top:0; width:100%;
  will-change:transform;
}
.cycler-stack{
  font-family:'Alegreya', Georgia, serif;   /* the frame sets the stack in a serif */
  font-size:17px;
}
.cycler-stack .row{
  height:var(--row-h); line-height:var(--row-h);
  white-space:nowrap;
  /* A row strengthens as it climbs: fainter and lighter at the bottom, black
     and bold at the centre, easing back toward grey on its way out. Opacity,
     colour and weight all ride --ease-throw, the same curve as the travel, so
     they land together instead of reading as three separate steps. */
  opacity:0;
  color: var(--grey-ramp);
  font-variation-settings:"wght" var(--row-wght-in);
  transition: opacity var(--dur-cycle) var(--ease-throw),
              color var(--dur-cycle) var(--ease-throw),
              font-variation-settings var(--dur-cycle) var(--ease-throw);
}
.cycler-stack .row.pos-in{                 /* below the centre, arriving */
  opacity: var(--row-opacity-in);
}
.cycler-stack .row.pos-out{                /* above the centre, leaving */
  opacity: var(--row-opacity-out);
  font-variation-settings:"wght" var(--row-wght-out);
}
.cycler-stack .row.is-active{              /* the centre line */
  opacity: 1;
  color: var(--ink);
  font-variation-settings:"wght" var(--row-wght-on);
}

/* the mascot's walk-in: a canvas so the matte composites over the b behind it */
.walk{ display:block; width:100%; height:100%; }

/* ---------- ticker, phone: a horizontal slide ----------
   No window, no weight ramp and no highlighted centre — every entry is the
   same on phone. The track carries the list twice and is translated by
   exactly one list-width, so the wrap lands on an identical arrangement. */
/* the entries dissolve at both ends rather than being cut by the frame edge */
.cycler-slide{
  overflow:hidden;
  --m: linear-gradient(to right,
        transparent 0,
        #000 var(--m-ticker-fade),
        #000 calc(100% - var(--m-ticker-fade)),
        transparent 100%);
  -webkit-mask-image: var(--m);
          mask-image: var(--m);
}
.cycler-slide .track{
  position:absolute; left:0; top:0;
  display:flex; align-items:flex-start;
  gap: var(--m-ticker-gap);
  white-space:nowrap;
  will-change:transform;
}
.cycler-slide .item{
  font-family:'Alegreya', Georgia, serif;
  font-size:17px;
  font-weight:500;
  color: var(--m-grey);
  white-space:nowrap;
}
