{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "column-reflow",
  "title": "Column Reflow",
  "description": "A justified paragraph reflows its column width, redistributing lines with even word spacing.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/column-reflow/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/Newsreader\";\nimport { useId } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: sans } = loadFont(\"normal\", {\n  weights: [\"400\"],\n  subsets: [\"latin\"],\n});\nconst { fontFamily: serif } = loadFont(\"italic\", {\n  weights: [\"400\"],\n  subsets: [\"latin\"],\n});\n\n// gradients.sh \"swirl\" palette (bg #07060a): #ffe2c4, #ff9a4d, #d2456b, #3a1f6b.\nconst SWIRL = [\"#ffe2c4\", \"#ff9a4d\", \"#d2456b\", \"#3a1f6b\"] as const;\n\nexport interface ColumnReflowProps {\n  text?: string;\n  accentWord?: string;\n  direction?: \"widen\" | \"narrow\";\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\nconst DEFAULT_TEXT =\n  \"Good motion starts with good typography. Give every word a measure, room to breathe, and a rhythm the eye can trust, then let the layout move and the story will read itself.\";\n\n// Newsreader 400 roman / italic metrics (1/1000 em), ASCII 32–126, plus\n// signed kerning pairs, measured with HarfBuzz from the served font files.\nconst NEWS = [\n  229, 270, 354, 566, 566, 780, 717, 198, 285, 285, 435, 512, 238, 354, 230,\n  401, 566, 566, 566, 566, 566, 566, 566, 566, 566, 566, 253, 268, 512, 512,\n  512, 440, 891, 710, 644, 705, 763, 640, 592, 751, 800, 338, 446, 730, 602,\n  979, 770, 776, 609, 776, 645, 554, 670, 768, 692, 966, 718, 684, 634, 328,\n  401, 330, 512, 410, 500, 460, 546, 437, 556, 462, 345, 496, 561, 280, 247,\n  540, 269, 832, 572, 526, 564, 544, 394, 384, 342, 536, 527, 743, 507, 524,\n  431, 322, 252, 322, 512,\n];\nconst NEWS_KERN = [\n  \"AC-32 AG-32 AJ27 AO-32 AQ-32 AT-101 AU-43 AV-88 AW-56 AY-100 Av-47\",\n  \"Aw-34 Ay-53 A’-72 BA-24 BV-24 BW-15 BX-24 BY-30 DA-43 DJ-44 DM-15\",\n  \"DV-30 DW-18 DX-52 DY-48 D.-40 D,-40 Ev-18 Ey-20 FA-58 FJ-68 Fa-49\",\n  \"Fc-42 Fd-44 Fe-42 Fg-41 Fi-19 Fm-42 Fn-42 Fo-42 Fp-38 Fq-44 Fr-42\",\n  \"Fs-42 Ft-18 Fu-23 Fv-16 Fw-17 Fx-28 Fy-18 Fz-38 F.-93 F,-93 GY-16\",\n  \"Gv-16 Gy-18 G’-16 Hc-20 Hd-20 He-20 Ho-20 Hp-15 Hq-20 Ht-16 Hv-23\",\n  \"Hw-24 Hy-26 Ic-20 Id-20 Ie-20 Io-20 Ip-15 Iq-20 It-16 Iv-23 Iw-24\",\n  \"Iy-26 JA-44 JJ-32 Ja-34 Jc-26 Jd-26 Je-26 Jf-19 Jg-22 Jm-20 Jn-20\",\n  \"Jo-26 Jp-20 Jq-26 Jr-20 Js-28 Jt-21 Ju-16 Jv-16 Jw-17 Jx-18 Jy-18\",\n  \"Jz-30 J.-43 J,-43 KC-66 KG-66 KJ23 KO-66 KQ-66 Kc-24 Ke-24 Ko-24 Ku-15\",\n  \"Kv-76 Kw-62 Ky-78 LT-96 LU-18 LV-88 LW-46 LY-102 Lv-45 Lw-27 Ly-46\",\n  \"L’-77 Mc-20 Md-17 Me-20 Mo-20 Mq-17 Mt-16 Mv-29 Mw-28 My-32 Na-30\",\n  \"Nc-20 Nd-20 Ne-20 Nf-16 Ng-18 Nm-16 Nn-16 No-20 Np-15 Nq-20 Nr-16\",\n  \"Ns-22 Nt-16 Nu-15 Nv-20 Nw-20 Ny-22 Nz-26 N.-26 N,-26 OA-34 OJ-33\",\n  \"OV-32 OW-19 OX-48 OY-50 O.-34 O,-34 PA-60 PJ-82 PX-31 Pa-27 Pc-41\",\n  \"Pd-46 Pe-41 Pg-21 Po-41 Pq-46 Ps-18 P.-104 P,-104 QA-34 QJ-33 QV-32\",\n  \"QW-19 QX-48 QY-50 Q.-34 RJ23 RV-24 RW-16 RY-28 SA-16 Sy-16 TA-100\",\n  \"TJ-76 Ta-92 Tc-106 Td-108 Te-106 Tf-30 Tg-86 Ti-15 Tm-66 Tn-66 To-106\",\n  \"Tp-70 Tq-108 Tr-66 Ts-82 Tt-66 Tu-64 Tv-78 Tw-74 Tx-68 Ty-78 Tz-69\",\n  \"T.-82 T,-82 UA-48 UJ-38 Ua-34 Uc-28 Ud-28 Ue-28 Uf-20 Ug-24 Um-20\",\n  \"Un-20 Uo-28 Up-20 Uq-28 Ur-20 Us-29 Ut-20 Uu-16 Uv-16 Uw-17 Ux-20\",\n  \"Uy-18 Uz-30 U.-47 U,-47 VA-83 VC-33 VG-33 VJ-70 VO-33 VQ-33 Va-79 Vb30\",\n  \"Vc-77 Vd-80 Ve-77 Vf-42 Vg-68 Vm-68 Vn-68 Vo-77 Vp-63 Vq-80 Vr-68\",\n  \"Vs-75 Vt-62 Vu-48 Vv-46 Vw-50 Vx-48 Vy-51 Vz-70 V.-84 V,-84 WA-54\",\n  \"WC-20 WG-20 WJ-44 WO-20 WQ-20 Wa-54 Wb26 Wc-42 Wd-42 We-42 Wf-30 Wg-38\",\n  \"Wm-34 Wn-34 Wo-42 Wp-31 Wq-42 Wr-34 Ws-44 Wt-36 Wu-29 Wv-22 Ww-24\",\n  \"Wx-30 Wy-26 Wz-38 W.-64 W,-64 XC-50 XG-50 XO-50 XQ-50 Xc-26 Xd-16\",\n  \"Xe-26 Xo-26 Xq-16 Xt-16 Xu-18 Xv-70 Xw-61 Xy-77 YA-92 YC-50 YG-50\",\n  \"YJ-68 YO-50 YQ-50 YS-16 Ya-101 Yb20 Yc-114 Yd-114 Ye-114 Yf-62 Yg-96\",\n  \"Ym-78 Yn-78 Yo-114 Yp-84 Yq-114 Yr-78 Ys-92 Yt-81 Yu-74 Yv-78 Yw-80\",\n  \"Yx-76 Yy-83 Yz-82 Y.-78 Y,-78 Zv-42 Zw-33 Zy-44 aT-75 aU-25 aV-93\",\n  \"aW-45 aY-90 av-28 aw-22 ay-30 a’-54 bA-18 bB-20 bD-20 bE-20 bF-20\",\n  \"bH-20 bI-20 bK-20 bL-20 bM-20 bN-20 bP-20 bR-20 bT-101 bU-28 bV-74\",\n  \"bW-43 bX-31 bY-120 bZ-32 bv-16 bx-23 by-21 b’-42 cT-80 cU-26 cV-69\",\n  \"cW-46 cY-98 eT-90 eU-29 eV-74 eW-48 eY-108 eZ-20 ey-20 e’-32 fB110\",\n  \"fD110 fE110 fF110 fH110 fI110 fK110 fL110 fM88 fN106 fP110 fR110 fS22\",\n  \"fT108 fU127 fV163 fW157 fX130 fY160 fZ85 fc-20 fd-22 fe-20 fi69 fj80\",\n  \"fo-20 fq-22 f.-28 f,-28 f’133 gA-21 gB-16 gD-16 gE-16 gF-16 gH-16\",\n  \"gI-16 gJ-24 gK-16 gL-16 gM-17 gN-16 gP-16 gR-16 gT-81 gU-16 gV-46\",\n  \"gW-24 gX-33 gY-90 gd-16 gq-16 g.-43 hT-76 hU-23 hV-94 hW-50 hY-94\",\n  \"hv-22 hw-21 hy-25 h’-52 kT-68 kU-24 kV-64 kW-32 kY-84 ka-20 kc-32\",\n  \"kd-28 ke-32 kg-20 ko-32 kq-28 ks-19 kt-18 kv-15 ky-17 k’-32 lU-18\",\n  \"l’-15 mT-76 mU-23 mV-94 mW-50 mY-94 mv-22 mw-21 my-25 m’-52 nT-76\",\n  \"nU-23 nV-94 nW-50 nY-94 nv-22 nw-21 ny-25 n’-52 oA-16 oB-20 oD-20\",\n  \"oE-20 oF-20 oH-20 oI-20 oK-20 oL-20 oM-20 oN-21 oP-20 oR-20 oT-106\",\n  \"oU-28 oV-78 oW-44 oX-32 oY-123 oZ-33 ov-20 ow-17 ox-23 oy-24 o’-42\",\n  \"pA-18 pB-20 pD-20 pE-20 pF-20 pH-20 pI-20 pK-20 pL-20 pM-20 pN-20\",\n  \"pP-20 pR-20 pT-101 pU-28 pV-74 pW-43 pX-31 pY-120 pZ-32 pv-16 px-23\",\n  \"py-21 p’-42 qT-71 qU-16 qV-67 qW-36 qY-99 rA-32 rB-26 rD-26 rE-26\",\n  \"rF-26 rH-26 rI-26 rJ-45 rK-26 rL-26 rM-29 rN-26 rP-26 rR-26 rT-79\",\n  \"rU-20 rV-60 rW-34 rX-46 rY-98 rZ-18 rc-17 rd-20 re-17 ro-17 rq-20\",\n  \"r.-50 r,-50 sN-15 sT-74 sU-28 sV-72 sW-42 sY-96 s’-22 tT-79 tU-22\",\n  \"tV-59 tW-35 tY-90 uT-69 uU-20 uV-74 uW-42 uY-95 u’-34 vA-46 vB-34\",\n  \"vD-34 vE-34 vF-34 vH-34 vI-34 vJ-50 vK-34 vL-34 vM-38 vN-35 vP-34\",\n  \"vR-34 vT-77 vU-25 vV-60 vW-35 vX-74 vY-93 vZ-22 va-22 vc-31 vd-32\",\n  \"ve-31 vg-18 vh-21 vk-21 vl-21 vo-31 vq-32 v.-60 v,-60 wA-33 wB-28\",\n  \"wD-28 wE-28 wF-28 wH-28 wI-28 wJ-42 wK-28 wL-28 wM-34 wN-30 wP-28\",\n  \"wR-28 wT-74 wU-22 wV-54 wW-32 wX-60 wY-88 wZ-17 wa-17 wc-22 wd-26\",\n  \"we-22 wh-16 wk-16 wl-16 wo-22 wq-26 w.-50 w,-50 xT-68 xU-22 xV-54\",\n  \"xW-32 xY-84 xc-24 xd-25 xe-24 xo-24 xq-25 yA-42 yB-30 yD-30 yE-30\",\n  \"yF-30 yH-30 yI-30 yJ-48 yK-30 yL-30 yM-35 yN-30 yP-30 yR-30 yT-74\",\n  \"yU-21 yV-54 yW-31 yX-70 yY-89 yZ-18 ya-16 yc-24 yd-28 ye-24 yh-16\",\n  \"yk-16 yl-16 yo-24 yq-28 y.-55 y,-55 zT-70 zU-30 zV-68 zW-38 zY-87\",\n  \".C-34 .G-34 .O-34 .Q-34 .T-82 .U-48 .V-88 .W-64 .Y-86 .j-18 .p-18\",\n  \".t-18 .u-21 .v-61 .w-50 .y-62 .’-115 ,C-34 ,G-34 ,O-34 ,Q-34 ,T-82\",\n  \",U-48 ,V-88 ,W-64 ,Y-86 ,j-18 ,p-18 ,t-18 ,u-21 ,v-61 ,w-50 ,y-62\",\n  \",’-115 ’A-76 ’J-61 ’V24 ’W17 ’Y20 ’a-60 ’c-67 ’d-74 ’e-67 ’f-20 ’g-64\",\n  \"’m-45 ’n-45 ’o-67 ’p-41 ’q-74 ’r-45 ’s-51 ’t-24 ’u-18 ’x-22 ’y-16\",\n  \"’z-28 ’.-124 ’,-124\",\n].join(\" \");\nconst NEWS_EXTRA: Record<string, number> = {\n  \"’\": 237,\n  \"‘\": 236,\n  \"“\": 410,\n  \"”\": 412,\n  \"–\": 520,\n  \"—\": 686,\n  \"…\": 641,\n};\nconst NEWS_ITALIC = [\n  233, 268, 345, 566, 566, 807, 687, 191, 276, 276, 432, 516, 232, 344, 224,\n  406, 566, 566, 566, 566, 566, 566, 566, 650, 566, 566, 256, 266, 516, 516,\n  516, 446, 909, 677, 625, 682, 744, 624, 574, 733, 772, 333, 424, 696, 570,\n  942, 742, 753, 590, 752, 632, 541, 634, 737, 662, 919, 680, 652, 610, 318,\n  376, 320, 516, 390, 500, 494, 468, 378, 498, 397, 308, 420, 498, 285, 256,\n  476, 260, 767, 538, 451, 509, 464, 396, 348, 318, 516, 470, 666, 486, 477,\n  420, 316, 250, 315, 516,\n];\nconst NEWS_ITALIC_KERN = [\n  \"AC-52 AG-52 AJ16 AO-52 AQ-52 AS-19 AT-92 AU-54 AV-89 AW-56 AY-95 Ab-16\",\n  \"Ac-21 Ae-21 Af-38 Am-16 An-16 Ao-21 Ap-16 Ar-16 At-22 Au-18 Av-24\",\n  \"Aw-24 Ay-50 A’-82 BA-23 BV-37 BW-24 BX-19 BY-47 DA-22 DJ-20 DT-16\",\n  \"DV-52 DW-29 DX-55 DY-70 D.-18 D,-18 Ey-16 FA-44 FJ-46 Fa-37 Fc-38\",\n  \"Fd-37 Fe-38 Fg-36 Fm-22 Fn-22 Fo-38 Fp-22 Fq-37 Fr-22 Fs-38 Fu-26\",\n  \"Fv-20 Fw-19 Fx-34 Fy-18 Fz-36 F.-82 F,-82 GV-20 GY-23 G’-18 Ha-18\",\n  \"Hc-19 Hd-18 He-19 Hf-21 Hg-20 Hm-16 Hn-16 Ho-19 Hp-16 Hq-18 Hr-16\",\n  \"Ht-22 Hu-16 Hv-15 Hy-30 Hz-15 Ia-18 Ic-19 Id-18 Ie-19 If-21 Ig-20\",\n  \"Im-16 In-16 Io-19 Ip-16 Iq-18 Ir-16 It-22 Iu-16 Iv-15 Iy-30 Iz-15\",\n  \"JA-28 JJ-18 Ja-21 Jc-22 Jd-21 Je-22 Jg-21 Jo-22 Jq-21 Js-26 Jt-15\",\n  \"Jy-18 Jz-24 J.-30 J,-30 KC-65 KG-65 KO-64 KQ-64 KV-30 KW-26 KY-34\",\n  \"Kc-35 Ke-35 Kf-44 Kg-22 Km-22 Kn-22 Ko-35 Kp-22 Kr-22 Kt-30 Ku-32\",\n  \"Kv-32 Kw-32 Ky-81 LC-15 LG-15 LT-94 LU-20 LV-87 LW-54 LY-101 Lf-34\",\n  \"Lm-16 Ln-16 Lp-16 Lr-16 Ly-57 L’-83 MC-17 MG-17 MO-16 MQ-16 Ma-17\",\n  \"Mc-19 Md-17 Me-19 Mf-29 Mg-20 Mm-16 Mn-16 Mo-19 Mp-16 Mq-17 Mr-16\",\n  \"Mt-26 Mu-16 Mv-16 Mw-16 My-36 Na-17 Nc-18 Nd-17 Ne-18 Nf-16 Ng-18\",\n  \"No-18 Nq-17 Ns-24 Nt-18 Ny-23 Nz-25 N.-20 N,-20 OA-18 OJ-19 OT-16\",\n  \"OV-51 OW-28 OX-52 OY-67 O.-15 O,-15 PA-46 PJ-46 PM-18 PV-23 PW-16\",\n  \"PX-61 PY-20 Pa-32 Pd-32 Pq-32 P.-88 P,-88 QA-18 QJ-19 QT-16 QV-51\",\n  \"QW-28 QX-52 QY-67 Qj42 Q.-15 Q,35 RJ16 RT-19 RU-22 RV-50 RW-28 RY-50\",\n  \"Ry-26 R’-34 SV-16 SY-15 Sf-16 TA-84 TJ-55 Ta-77 Tc-66 Td-77 Te-66\",\n  \"Tg-64 Tm-60 Tn-60 To-66 Tp-60 Tq-77 Tr-60 Ts-66 Tt-15 Tu-61 Tv-62\",\n  \"Tw-62 Tx-62 Ty-65 Tz-68 T.-70 T,-70 UA-42 UJ-19 Ua-22 Uc-23 Ud-22\",\n  \"Ue-23 Ug-22 Uo-23 Uq-22 Us-26 Ux-16 Uy-18 Uz-24 U.-34 U,-34 VA-84\",\n  \"VC-20 VG-20 VJ-70 VO-18 VQ-18 Va-70 Vb36 Vc-63 Vd-70 Ve-63 Vf-22 Vg-62\",\n  \"Vh25 Vk25 Vl20 Vm-58 Vn-58 Vo-63 Vp-58 Vq-70 Vr-58 Vs-69 Vt-44 Vu-63\",\n  \"Vv-58 Vw-58 Vx-70 Vy-59 Vz-66 V.-77 V,-77 WA-62 WC-15 WG-15 WJ-44\",\n  \"Wa-37 Wb32 Wc-38 Wd-37 We-38 Wg-38 Wh22 Wk22 Wl16 Wm-32 Wn-32 Wo-38\",\n  \"Wp-32 Wq-37 Wr-32 Ws-42 Wt-26 Wu-33 Wv-34 Ww-34 Wx-40 Wy-34 Wz-40\",\n  \"W.-57 W,-57 XC-48 XG-48 XO-48 XQ-48 Xc-33 Xe-33 Xf-42 Xg-24 Xm-24\",\n  \"Xn-24 Xo-33 Xp-24 Xr-24 Xt-32 Xu-28 Xv-31 Xw-30 Xy-71 YA-98 YC-36\",\n  \"YG-36 YJ-66 YO-30 YQ-30 Ya-100 Yb27 Yc-98 Yd-100 Ye-98 Yf-40 Yg-91\",\n  \"Yh16 Yk16 Ym-78 Yn-78 Yo-98 Yp-78 Yq-100 Yr-78 Ys-84 Yt-69 Yu-83 Yv-79\",\n  \"Yw-79 Yx-86 Yy-92 Yz-88 Y.-72 Y,-72 Zf-34 Zm-18 Zn-18 Zp-18 Zr-18\",\n  \"Zt-22 Zu-16 Zy-32 Zz-15 aT-70 aU-19 aV-78 aW-36 aY-86 af-23 ay-22\",\n  \"a’-58 bB-19 bD-19 bE-19 bF-19 bH-19 bI-19 bK-19 bL-19 bM-16 bN-20\",\n  \"bP-19 bR-19 bT-89 bU-26 bV-69 bW-37 bX-23 bY-108 bZ-34 b’-39 cT-76\",\n  \"cU-32 cV-78 cW-45 cY-94 c’-25 dT-17 dU-20 dV-20 dW-18 dY-24 df-16\",\n  \"dy-25 d’-26 eT-76 eU-34 eV-76 eW-45 eY-96 eZ-15 e’-30 fB81 fD81 fE81\",\n  \"fF81 fH81 fI81 fK81 fL81 fM62 fN87 fP81 fR81 fS60 fT104 fU96 fV136\",\n  \"fW129 fX102 fY132 fZ74 fi56 fj66 f’130 gA-18 gB-16 gD-16 gE-16 gF-16\",\n  \"gH-16 gI-16 gJ-25 gK-16 gL-16 gN-17 gP-16 gR-16 gT-71 gU-16 gV-48\",\n  \"gW-22 gX-28 gY-83 gZ-15 gf56 gg40 gj86 gy18 g.-22 g,62 hT-76 hU-22\",\n  \"hV-88 hW-39 hY-92 hf-20 hy-20 h’-64 iT-22 iU-19 iY-15 iy-18 i’-24\",\n  \"kT-66 kU-25 kV-74 kW-34 kY-85 k’-61 lC-18 lG-18 lO-18 lQ-18 lT-28\",\n  \"lU-26 lV-33 lW-29 lY-36 lf-26 ly-36 l’-28 mT-76 mU-22 mV-88 mW-39\",\n  \"mY-92 mf-20 my-20 m’-64 nT-76 nU-22 nV-88 nW-39 nY-92 nf-20 ny-20\",\n  \"n’-64 oB-18 oD-18 oE-18 oF-18 oH-18 oI-18 oK-18 oL-18 oN-20 oP-18\",\n  \"oR-18 oT-92 oU-28 oV-78 oW-38 oY-111 oZ-26 of-24 o’-48 pB-18 pD-18\",\n  \"pE-18 pF-18 pH-18 pI-18 pK-18 pL-18 pN-20 pP-18 pR-18 pT-89 pU-26\",\n  \"pV-72 pW-36 pX-16 pY-109 pZ-30 pf-15 p’-38 qN-16 qT-78 qU-26 qV-74\",\n  \"qW-46 qY-106 qZ-19 qj44 rA-52 rB-24 rD-24 rE-24 rF-24 rH-24 rI-24\",\n  \"rJ-57 rK-24 rL-24 rM-26 rN-26 rP-24 rR-24 rT-76 rU-18 rV-60 rW-28\",\n  \"rX-74 rY-96 rZ-27 r.-56 r,-56 sT-72 sU-33 sV-72 sW-46 sY-92 s’-32\",\n  \"tT-61 tU-24 tV-62 tW-40 tY-83 uJ18 uT-69 uU-22 uV-83 uW-36 uY-85 uf-20\",\n  \"uy-28 u’-64 vA-16 vB-22 vD-22 vE-22 vF-22 vH-22 vI-22 vK-22 vL-22\",\n  \"vM-20 vN-23 vP-22 vR-22 vT-88 vU-29 vV-69 vW-42 vX-35 vY-110 vZ-38\",\n  \"v’-26 wA-15 wB-22 wD-22 wE-22 wF-22 wH-22 wI-22 wK-22 wL-22 wM-20\",\n  \"wN-23 wP-22 wR-22 wT-90 wU-30 wV-72 wW-42 wX-31 wY-112 wZ-38 w’-26\",\n  \"xT-68 xU-24 xV-72 xW-38 xY-86 x’-19 yN-16 yT-75 yU-22 yV-68 yW-41\",\n  \"yY-104 yZ-24 zT-72 zU-30 zV-74 zW-40 zY-82 zy-22 z’-38 .C-56 .G-56\",\n  \".O-56 .Q-56 .S-20 .T-88 .U-59 .V-89 .W-66 .Y-89 .b-20 .c-22 .e-22 .f18\",\n  \".g-23 .i-30 .j-36 .m-37 .n-37 .o-22 .p-37 .r-37 .t-32 .u-34 .v-34\",\n  \".w-34 .y-79 .z-18 .’-138 ,C-56 ,G-56 ,O-56 ,Q-56 ,S-20 ,T-88 ,U-59\",\n  \",V-89 ,W-66 ,Y-89 ,b-20 ,c-22 ,e-22 ,f41 ,g-23 ,i-30 ,j41 ,m-37 ,n-37\",\n  \",o-22 ,p-37 ,r-37 ,t-32 ,u-34 ,v-34 ,w-34 ,y-79 ,z-18 ,’-138 ’A-68\",\n  \"’J-56 ’a-68 ’c-53 ’d-68 ’e-53 ’g-46 ’m-28 ’n-28 ’o-53 ’p-28 ’q-68\",\n  \"’r-28 ’s-34 ’u-40 ’v-28 ’w-28 ’x-47 ’y-22 ’z-34 ’.-112 ’,-112\",\n].join(\" \");\nconst NEWS_ITALIC_EXTRA: Record<string, number> = {\n  \"’\": 236,\n  \"‘\": 234,\n  \"“\": 408,\n  \"”\": 409,\n  \"–\": 499,\n  \"—\": 656,\n  \"…\": 633,\n};\n\nfunction kernMap(pairs: string) {\n  return new Map(\n    pairs.split(\" \").map((p) => [p.slice(0, 2), Number(p.slice(2)) / 1000]),\n  );\n}\nconst KERN_SANS = kernMap(NEWS_KERN);\nconst KERN_SERIF = kernMap(NEWS_ITALIC_KERN);\n\nconst TRACK = -0.008;\nconst SERIF_SCALE = 1.04;\nconst SPACE = 0.229 + TRACK;\nconst NARROW_EM = 9.6;\nconst LEADING = 1.3;\nconst BASELINE_SANS = 0.735;\nconst BASELINE_SERIF = 0.735;\n\nconst ENTER = -4;\nconst X_START = 22;\nconst X_DUR = 24;\nconst X_ROW = 1.5;\nconst X_WORD = 0.7;\nconst Y_START = 44;\nconst Y_DUR = 26;\nconst Y_ROW = 2;\n\nconst EASE_IO = Easing.bezier(0.65, 0, 0.35, 1);\nconst EASE_Y = Easing.bezier(0.55, 0, 0.25, 1);\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\nconst THEMES = {\n  dark: {\n    bg: \"#0A0A0B\",\n    key: \"rgba(255,255,255,0.05)\",\n    fg: \"#F3F2EF\",\n    muted: \"#8B8A90\",\n    hair: \"rgba(255,255,255,0.16)\",\n    grain: 0.07,\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    key: \"rgba(255,255,255,0.9)\",\n    fg: \"#121214\",\n    muted: \"#6E6C67\",\n    hair: \"rgba(18,18,20,0.16)\",\n    grain: 0.045,\n  },\n} as const;\n\nfunction advance(\n  text: string,\n  table: number[],\n  extra: Record<string, number>,\n  kern: Map<string, number>,\n  track: number,\n) {\n  let w = 0;\n  let prev = \"\";\n  for (const ch of text) {\n    w += kern.get(prev + ch) ?? 0;\n    prev = ch;\n    const code = ch.charCodeAt(0);\n    const a = code >= 32 && code < 127 ? table[code - 32] : (extra[ch] ?? 600);\n    w += a / 1000 + track;\n  }\n  return w;\n}\n\nconst sansEm = (s: string) => advance(s, NEWS, NEWS_EXTRA, KERN_SANS, TRACK);\nconst serifEm = (s: string) =>\n  advance(s, NEWS_ITALIC, NEWS_ITALIC_EXTRA, KERN_SERIF, 0) * SERIF_SCALE;\n\ninterface Word {\n  core: string;\n  tail: string;\n  serif: boolean;\n  w: number;\n}\n\nfunction strip(s: string) {\n  return s.toLowerCase().replace(/[^\\p{L}\\p{N}]/gu, \"\");\n}\n\nfunction toWords(text: string, accentWord: string): Word[] {\n  const target = strip(accentWord);\n  let used = false;\n  return text\n    .trim()\n    .split(/\\s+/)\n    .map((raw) => {\n      const isAccent = !used && target.length > 0 && strip(raw) === target;\n      if (!isAccent) {\n        return { core: raw, tail: \"\", serif: false, w: sansEm(raw) };\n      }\n      used = true;\n      const m = /^(.*?)([^\\p{L}\\p{N}]*)$/u.exec(raw);\n      const core = m ? m[1] : raw;\n      const tail = m ? m[2] : \"\";\n      return {\n        core,\n        tail,\n        serif: true,\n        w: serifEm(core) + (tail ? 0.04 + sansEm(tail) : 0),\n      };\n    });\n}\n\n/**\n * Break into justified rows with the least stretched word spacing (a small\n * Knuth–Plass: dynamic programming over break points). Returns the cost too,\n * so the caller can try a few measures and keep the evenest column.\n */\nfunction breakRows(words: Word[], measure: number) {\n  const n = words.length;\n  const best = new Array<number>(n + 1).fill(Number.POSITIVE_INFINITY);\n  const from = new Array<number>(n + 1).fill(0);\n  best[0] = 0;\n  for (let j = 1; j <= n; j++) {\n    let width = -SPACE;\n    for (let i = j - 1; i >= 0; i--) {\n      width += words[i].w + SPACE;\n      if (width > measure && i < j - 1) break;\n      const count = j - i;\n      let cost = 0;\n      if (j < n) {\n        const gap = count > 1 ? (measure - width) / (count - 1) + SPACE : 9;\n        const stretch = (gap - SPACE) / SPACE;\n        cost = stretch * stretch + (count < 3 ? 1.5 : 0);\n      } else if (width < measure * 0.3) {\n        cost = 1;\n      }\n      if (best[i] + cost < best[j]) {\n        best[j] = best[i] + cost;\n        from[j] = i;\n      }\n    }\n  }\n  const rows: number[][] = [];\n  for (let j = n; j > 0; j = from[j]) {\n    const row: number[] = [];\n    for (let k = from[j]; k < j; k++) row.push(k);\n    rows.unshift(row);\n  }\n  return { rows, cost: best[n] };\n}\n\nfunction chooseMeasure(words: Word[]) {\n  let pick = { measure: NARROW_EM, rows: breakRows(words, NARROW_EM).rows };\n  let low = Number.POSITIVE_INFINITY;\n  for (let m = NARROW_EM - 1; m <= NARROW_EM + 1.001; m += 0.1) {\n    const { rows, cost } = breakRows(words, m);\n    const paired: number[] = [];\n    for (let r = 0; r + 1 < rows.length - 1; r += 2) {\n      paired.push(rowWidth(words, [...rows[r], ...rows[r + 1]]));\n    }\n    const top = Math.max(1, ...paired);\n    const rag = paired.reduce((s, w) => s + ((top - w) / top) ** 2, 0);\n    const score = cost + rag * 40 + Math.abs(m - NARROW_EM) * 0.4;\n    if (rows.length > 1 && score < low) {\n      low = score;\n      pick = { measure: m, rows };\n    }\n  }\n  return pick;\n}\n\nfunction rowWidth(words: Word[], row: number[]) {\n  return row.reduce((s, i) => s + words[i].w, 0) + SPACE * (row.length - 1);\n}\n\n/** x offsets (em) of each word in a row, justified unless it is the last. */\nfunction setRow(words: Word[], row: number[], measure: number, last: boolean) {\n  const sum = row.reduce((s, i) => s + words[i].w, 0);\n  const gap =\n    last || row.length < 2 ? SPACE : (measure - sum) / (row.length - 1);\n  const xs: number[] = [];\n  let x = 0;\n  for (const i of row) {\n    xs.push(x);\n    x += words[i].w + gap;\n  }\n  return xs;\n}\n\ninterface Placed {\n  x0: number;\n  y0: number;\n  x1: number;\n  y1: number;\n  row: number;\n  slot: number;\n  rowLen: number;\n  dir: number;\n}\n\n/**\n * The narrow column is justified; the wide paragraph pairs its rows (row 2t\n * and 2t + 1 become line t) set with natural spacing. Pairing is what keeps\n * the move clean: odd rows slide out sideways into empty space, then both\n * halves close vertically, so no word ever crosses another.\n */\nfunction layout(words: Word[]) {\n  const { measure: narrow, rows } = chooseMeasure(words);\n  const R = rows.length;\n  const T = Math.ceil(R / 2);\n  const lines = Array.from({ length: T }, (_, t) => [\n    ...rows[2 * t],\n    ...(rows[2 * t + 1] ?? []),\n  ]);\n  const wide = Math.max(...lines.map((l) => rowWidth(words, l)));\n  const placed: Placed[] = [];\n  const narrowX: number[] = [];\n  const narrowRow: number[] = [];\n  rows.forEach((row, r) => {\n    const xs = setRow(words, row, narrow, r === R - 1);\n    row.forEach((i, k) => {\n      narrowX[i] = xs[k] - narrow / 2;\n      narrowRow[i] = r;\n    });\n  });\n  lines.forEach((line, t) => {\n    const xs = setRow(words, line, wide, true);\n    line.forEach((i, k) => {\n      const r = narrowRow[i];\n      const own = rows[r];\n      placed[i] = {\n        x0: narrowX[i],\n        y0: r - (R - 1) / 2,\n        x1: xs[k] - wide / 2,\n        y1: t - (T - 1) / 2,\n        row: r,\n        slot: own.indexOf(i),\n        rowLen: own.length,\n        dir: r % 2 === 0 ? -1 : 1,\n      };\n    });\n  });\n  return { placed, R, T, wide, narrow };\n}\n\n/** Motion progress for a word at virtual time v (widen direction). */\nfunction progress(p: Placed, R: number, v: number) {\n  const order = p.dir < 0 ? p.slot : p.rowLen - 1 - p.slot;\n  const xs = X_START + p.row * X_ROW + order * X_WORD;\n  const px = interpolate(v, [xs, xs + X_DUR], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const ys = Y_START + Math.abs(p.row - (R - 1) / 2) * Y_ROW;\n  const py = interpolate(v, [ys, ys + Y_DUR], [0, 1], {\n    ...CLAMP,\n    easing: EASE_Y,\n  });\n  return { px, py };\n}\n\nfunction motionEnd(R: number) {\n  return Y_START + ((R - 1) / 2) * Y_ROW + Y_DUR;\n}\n\nfunction Grain({ opacity }: { opacity: number }) {\n  const id = `g${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n  return (\n    <svg\n      width=\"100%\"\n      height=\"100%\"\n      style={{ position: \"absolute\", inset: 0, opacity, pointerEvents: \"none\" }}\n      aria-hidden\n    >\n      <filter id={id}>\n        <feTurbulence\n          type=\"fractalNoise\"\n          baseFrequency=\"0.9\"\n          numOctaves={2}\n          seed={5}\n          stitchTiles=\"stitch\"\n        />\n        <feColorMatrix values=\"0 0 0 0 0.5  0 0 0 0 0.5  0 0 0 0 0.5  0 0 0 0.9 0\" />\n      </filter>\n      <rect width=\"100%\" height=\"100%\" filter={`url(#${id})`} />\n    </svg>\n  );\n}\n\nexport function ColumnReflow({\n  text = DEFAULT_TEXT,\n  accentWord = \"room\",\n  direction = \"widen\",\n  theme = \"dark\",\n  speed = 1,\n}: ColumnReflowProps) {\n  const t = useCurrentFrame() * speed;\n  const { width: W, height: H } = useVideoConfig();\n  const u = H / 720;\n  const c = THEMES[theme];\n\n  const words = toWords(text, accentWord);\n  const { placed, R, T, wide, narrow } = layout(words);\n  const F = Math.min(56 * u, (W * 0.86) / wide, (H * 0.74) / (R * LEADING));\n  const L = F * LEADING;\n  const cx = W / 2;\n  const cy = H / 2 + 0.28 * F;\n\n  const end = motionEnd(R);\n  const narrowing = direction === \"narrow\";\n  const v = narrowing ? X_START + end - Math.min(Math.max(t, X_START), end) : t;\n\n  const settle = interpolate(t, [end - 6, end + 14], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  // The accent word develops into a swirl gradient as the paragraph lands;\n  // the warm core drifts a few percent across the hold.\n  const drift = interpolate(t, [end - 6, end + 60], [0, 1], CLAMP);\n  const haze = interpolate(t, [ENTER, end + 10], [0.35, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const swirl =\n    theme === \"dark\"\n      ? `linear-gradient(100deg, ${SWIRL[0]} ${6 + 8 * drift}%, ${SWIRL[1]} ${44 + 8 * drift}%, ${SWIRL[2]} 96%)`\n      : `linear-gradient(100deg, ${SWIRL[1]} ${4 + 8 * drift}%, ${SWIRL[2]} ${48 + 8 * drift}%, ${SWIRL[3]} 100%)`;\n\n  // Measure guides follow the column edges.\n  const edge = interpolate(\n    v,\n    [X_START, X_START + (R - 1) * X_ROW + X_DUR],\n    [0, 1],\n    { ...CLAMP, easing: EASE_IO },\n  );\n  const tall = interpolate(v, [Y_START, end], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const halfW = interpolate(edge, [0, 1], [narrow / 2, wide / 2]) * F;\n  const halfH =\n    (interpolate(tall, [0, 1], [(R - 1) / 2, (T - 1) / 2]) + 0.95) * L;\n  const guideIn = interpolate(t, [ENTER + 4, ENTER + 22], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const guideOut = interpolate(t, [end - 4, end + 16], [1, 0], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const hair = Math.max(1, u);\n\n  return (\n    <AbsoluteFill style={{ background: c.bg, overflow: \"hidden\" }}>\n      <AbsoluteFill\n        style={{\n          background: `radial-gradient(ellipse 70% 80% at 40% 35%, ${c.key}, transparent 70%)`,\n        }}\n      />\n      <AbsoluteFill\n        style={{\n          background: [\n            `radial-gradient(70% 60% at ${88 - 6 * drift}% 112%, color-mix(in oklab, ${SWIRL[2]} ${theme === \"dark\" ? 30 : 16}%, transparent) 0%, transparent 70%)`,\n            `radial-gradient(60% 55% at ${8 + 4 * drift}% -12%, color-mix(in oklab, ${theme === \"dark\" ? SWIRL[3] : SWIRL[0]} ${theme === \"dark\" ? 55 : 60}%, transparent) 0%, transparent 70%)`,\n          ].join(\", \"),\n          opacity: haze,\n        }}\n      />\n\n      {[-1, 1].map((side) => (\n        <div\n          key={`g${side}`}\n          style={{\n            position: \"absolute\",\n            left: cx + side * (halfW + 0.55 * F) - hair / 2,\n            top: cy - 0.36 * F - halfH * guideIn,\n            width: hair,\n            height: halfH * 2 * guideIn,\n            background: c.hair,\n            opacity: guideOut,\n          }}\n        />\n      ))}\n\n      {words.map((w, i) => {\n        const p = placed[i];\n        const { px, py } = progress(p, R, v);\n        const x = cx + interpolate(px, [0, 1], [p.x0, p.x1]) * F;\n        const y = cy + interpolate(py, [0, 1], [p.y0, p.y1]) * L;\n        const startRow = narrowing ? Math.round(p.y1 + (T - 1) / 2) : p.row;\n        const enter = interpolate(\n          t,\n          [ENTER + startRow * 1.6, ENTER + startRow * 1.6 + 16],\n          [0, 1],\n          { ...CLAMP, easing: EASE_OUT },\n        );\n        const lift = (1 - enter) * 0.35 * F;\n        if (!w.serif) {\n          return (\n            <div\n              key={`${i}-${w.core}`}\n              style={{\n                position: \"absolute\",\n                left: x,\n                top: y - BASELINE_SANS * F + lift,\n                fontFamily: sans,\n                fontWeight: 400,\n                fontSize: F,\n                lineHeight: 1,\n                letterSpacing: `${TRACK}em`,\n                whiteSpace: \"pre\",\n                color: c.fg,\n                opacity: enter,\n              }}\n            >\n              {w.core}\n            </div>\n          );\n        }\n        const sF = F * SERIF_SCALE;\n        const serifType = {\n          position: \"absolute\",\n          left: x - 0.2 * sF,\n          top: y - BASELINE_SERIF * sF + lift,\n          paddingLeft: 0.2 * sF,\n          paddingRight: 0.2 * sF,\n          fontFamily: serif,\n          fontStyle: \"italic\",\n          fontWeight: 400,\n          fontSize: sF,\n          lineHeight: 1,\n          whiteSpace: \"pre\",\n        } as const;\n        return (\n          <div key={`${i}-${w.core}`}>\n            <div\n              style={{\n                ...serifType,\n                color: c.fg,\n                opacity: enter * (1 - settle),\n              }}\n            >\n              {w.core}\n            </div>\n            <div\n              style={{\n                ...serifType,\n                color: \"transparent\",\n                backgroundImage: swirl,\n                WebkitBackgroundClip: \"text\",\n                backgroundClip: \"text\",\n                opacity: enter * settle,\n              }}\n            >\n              {w.core}\n            </div>\n            {w.tail ? (\n              <div\n                style={{\n                  position: \"absolute\",\n                  left: x + (serifEm(w.core) + 0.04) * F,\n                  top: y - BASELINE_SANS * F + lift,\n                  fontFamily: sans,\n                  fontWeight: 400,\n                  fontSize: F,\n                  lineHeight: 1,\n                  letterSpacing: `${TRACK}em`,\n                  whiteSpace: \"pre\",\n                  color: c.fg,\n                  opacity: enter,\n                }}\n              >\n                {w.tail}\n              </div>\n            ) : null}\n          </div>\n        );\n      })}\n\n      <Grain opacity={c.grain} />\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/column-reflow.tsx"
    }
  ],
  "type": "registry:component"
}
