@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 600 700;
  font-stretch: 100% 125%;
  font-display: swap;
  src: url(fonts/archivo-600-700.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/ibmplexmono-400.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(fonts/ibmplexmono-500.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400;
  font-stretch: 100%;
  font-display: swap;
  src: url(fonts/ibmplexsans-400.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 500;
  font-stretch: 100%;
  font-display: swap;
  src: url(fonts/ibmplexsans-500.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Lato';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/lato-400.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Lato';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(fonts/lato-700.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Noto Sans SC';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/notosanssc-400.woff2) format('woff2');
  unicode-range: U+3000-3002, U+300c-300d, U+4e00-4e01, U+4e03, U+4e09-4e0b, U+4e0d-4e0e, U+4e13-4e14, U+4e16, U+4e1a, U+4e1c-4e1d, U+4e22, U+4e24, U+4e2a, U+4e2d, U+4e3a-4e3b, U+4e48-4e49, U+4e4b, U+4e58, U+4e5d, U+4e5f-4e60, U+4e70, U+4e86, U+4e8b-4e8c, U+4e8e, U+4e92, U+4e94, U+4e9a-4e9b, U+4ea4, U+4ea7, U+4eba, U+4ec0, U+4eca-4ecb, U+4ece, U+4ed6, U+4ed8, U+4ee3, U+4ee5, U+4eec, U+4ef6-4ef7, U+4efb, U+4efd, U+4f01, U+4f18, U+4f1a, U+4f20, U+4f30, U+4f38, U+4f3a, U+4f46, U+4f4d-4f4f, U+4f53, U+4f55, U+4f59, U+4f5c, U+4f60, U+4f7f, U+4f8b, U+4f9b, U+4fa7, U+4fbf, U+4fc3, U+4fdd, U+4fee, U+500d, U+5012, U+5019, U+503c, U+504f, U+505a, U+505c, U+50cf, U+5143, U+5145, U+5148, U+5165, U+5168, U+516b-516d, U+5171, U+5173, U+5176-5177, U+517b, U+5185, U+518d, U+5199, U+51b2-51b3, U+51b5, U+51b7, U+51c0, U+51c6, U+51cf, U+51d1, U+51e0, U+51fa, U+5200, U+5206-5207, U+5212, U+5217, U+5219, U+521b, U+522b, U+5230, U+5236-5237, U+524d, U+5269, U+526f, U+529b, U+529e-52a1, U+52a8-52a9, U+5305, U+5316, U+5339-533b, U+5341, U+534a, U+534e, U+5355-5357, U+5360, U+5370, U+5374, U+5377, U+5382, U+538b, U+539a, U+539f, U+53bb, U+53c2, U+53ca, U+53cc-53cd, U+53d1, U+53d6-53d8, U+53e0, U+53e3, U+53e5-53e6, U+53ea, U+53ef-53f0, U+53f3, U+53f7-53f8, U+5403-5404, U+5408, U+540c-540e, U+5411, U+5417, U+5426, U+542b, U+5439, U+544a, U+5458, U+5468, U+547d, U+548c, U+54ac, U+54c1, U+54cd, U+54ea, U+552e, U+5546, U+5668, U+56db, U+56de, U+56e0, U+56e2, U+56f4, U+56fa, U+56fd-56fe, U+5706, U+5728, U+5730, U+573a, U+573e, U+5740, U+5747, U+5750, U+5757, U+5783, U+578b, U+57df, U+57f9-57fa, U+5806, U+5854, U+586b, U+58a8, U+58c1, U+58eb, U+5904, U+5907, U+590d, U+5916, U+591a, U+591f, U+5927, U+5929, U+5934, U+5939, U+5957, U+597d, U+5982, U+59cb, U+59d3, U+5b50, U+5b54, U+5b57-5b58, U+5b66, U+5b83, U+5b88-5b89, U+5b8c, U+5b9a, U+5b9e, U+5ba2, U+5bb6, U+5bb9, U+5bbd, U+5bc4, U+5bf8-5bf9, U+5bfc, U+5c01, U+5c06, U+5c0f, U+5c11, U+5c31, U+5c3a, U+5c3e, U+5c42, U+5c4b, U+5c4f, U+5c71, U+5de1, U+5de5-5de6, U+5dee, U+5df1-5df2, U+5e02, U+5e08, U+5e26, U+5e38, U+5e45, U+5e55, U+5e72-5e74, U+5e76, U+5e7f, U+5e8a, U+5e8f, U+5e94-5e95, U+5e97, U+5e9f, U+5ea6, U+5f00, U+5f02, U+5f04, U+5f0f, U+5f15, U+5f20, U+5f3a, U+5f53, U+5f55, U+5f62, U+5f71, U+5f7c, U+5f80, U+5f84-5f85, U+5f88, U+5f97, U+5fae, U+5fc3, U+5fc5, U+5feb, U+600e, U+601d, U+6027, U+603b, U+60a8, U+60c5, U+60ef, U+60f3, U+610f, U+613f, U+6210-6211, U+6216, U+6237, U+6240-6241, U+624b, U+624d, U+6253, U+625b, U+626f, U+6279, U+627e, U+6280, U+628a, U+6293, U+6295, U+6298, U+62a4-62a5, U+62bb, U+62c5, U+62c9, U+62e2, U+62e5, U+62e9, U+62fc, U+62ff, U+6301, U+6307, U+6309, U+6311, U+6324, U+635f, U+6362, U+6389, U+6392, U+63a5, U+63a7-63a8, U+63d0, U+63d2, U+6491, U+6495, U+64cd, U+652f, U+6536, U+6539, U+653e, U+6545, U+6548, U+6559, U+655e, U+6563, U+6570, U+6574, U+6587, U+6599, U+65a4, U+65ad, U+65b0, U+65b9, U+65cb, U+65e0, U+65e5, U+65f6, U+660e, U+6613, U+6625, U+662f, U+663e, U+66f4, U+6700, U+6709, U+670d, U+671f, U+672a-672c, U+672f, U+673a, U+6742, U+6746, U+6750, U+6761, U+6765, U+677e-677f, U+6784, U+6795, U+679c, U+67b6, U+67d3, U+67e5, U+6807, U+680f, U+6837, U+683c, U+6848, U+684c, U+6863, U+6865, U+68b0, U+68c0, U+695a, U+6a21, U+6a2a, U+6b21, U+6b27, U+6b3e, U+6b62-6b65, U+6b6a, U+6b8a, U+6bb5, U+6bcd, U+6bcf, U+6bd4, U+6beb, U+6c14, U+6c27, U+6c34, U+6c38, U+6c42, U+6c47, U+6c49, U+6c5f, U+6c7d, U+6c9f, U+6ca1, U+6cd5, U+6ce1, U+6ce8, U+6cfd, U+6d3e, U+6d41, U+6d4b, U+6d66, U+6d6a, U+6d6e, U+6d77, U+6df1, U+6df7, U+6dfb, U+6e05, U+6e2f, U+6e32, U+6e38, U+6e7f, U+6e90, U+6ed1, U+7070, U+70b9, U+70e6, U+70ed, U+7136, U+7167, U+7194, U+7247-7248, U+724c, U+7262, U+7269, U+7275, U+7279, U+72b6, U+72ec, U+7387, U+73af-73b0, U+73c0, U+73ed, U+7406, U+7425, U+745e, U+74e3, U+751f, U+7528, U+7531, U+7535, U+753b, U+7545, U+754c, U+7559, U+767d, U+7684, U+76b1, U+76ca, U+76d6, U+76d8, U+76ee, U+76f4, U+76f8, U+7701, U+770b, U+771f, U+773c, U+7740, U+77e5, U+77ed-77ee, U+7801, U+7814, U+7840, U+786c, U+786e, U+793a, U+79bb, U+79cb, U+79cd, U+79d1, U+79f0, U+7a0b, U+7a33, U+7a7a, U+7a7f, U+7a84, U+7a97, U+7acb, U+7ad6, U+7ad9, U+7aef, U+7b14, U+7b26, U+7b2c, U+7b49, U+7b52, U+7b7e, U+7b80, U+7b97, U+7ba1, U+7bad, U+7bb1, U+7c73, U+7c7b, U+7c92, U+7c97-7c98, U+7cbe, U+7cca, U+7cfb, U+7d20, U+7d22, U+7d27, U+7ea2, U+7ea6, U+7eaf, U+7eb8, U+7ebf, U+7ec4, U+7ec6, U+7ec8, U+7ecf, U+7ed3, U+7ed9, U+7edf, U+7eed, U+7ef4, U+7f13, U+7f18, U+7f1d, U+7f29, U+7f6e, U+7ffb, U+8003, U+8005, U+800c, U+8010, U+8015, U+804a, U+8054, U+80cc, U+80fd, U+8109, U+811a, U+819c, U+81c2, U+81ea, U+81f3-81f4, U+820d, U+822a, U+8272, U+827a, U+82a6, U+82b1, U+82cf, U+82f1, U+8303, U+8349, U+8350, U+836f, U+843d, U+846b, U+8584, U+8651, U+8679, U+87ba, U+884c, U+8865, U+8868, U+888b, U+88ab, U+88c1-88c2, U+88c5, U+897f, U+8981, U+8986, U+89c1, U+89c4, U+89c6, U+89c8, U+89d2, U+89e3, U+8a00, U+8ba1-8ba2, U+8ba4, U+8ba9, U+8bad, U+8bb0, U+8bbe-8bbf, U+8bc1, U+8bc4, U+8bc9, U+8bcd, U+8bd5, U+8bdd, U+8be2, U+8be5-8be6, U+8bed, U+8bf4, U+8bfb, U+8c01, U+8c03, U+8d1f, U+8d23, U+8d27-8d28, U+8d2d, U+8d34, U+8d39, U+8d44, U+8d70, U+8d77, U+8d85, U+8d8a, U+8db3, U+8dd1, U+8ddd, U+8ddf, U+8de8, U+8def, U+8df3, U+8eab, U+8f66, U+8f6c, U+8f6e-8f6f, U+8f74, U+8f7b, U+8f83, U+8f85-8f86, U+8fa8, U+8fb9, U+8fbe, U+8fc7, U+8fd0-8fd1, U+8fd8-8fd9, U+8fdb-8fdc, U+8fde, U+8ffd, U+9000-9002, U+9009, U+900f-9010, U+9014, U+901a, U+901f-9020, U+904d, U+9053, U+9063, U+90a3, U+90ae, U+90e8, U+90fd, U+914d, U+9152, U+91c7, U+91cc-91cd, U+91cf, U+9488-9489, U+949f, U+94ae, U+94b1, U+94c1, U+94fe, U+9500, U+9510, U+952e, U+9547, U+957f, U+95e8, U+95ed-95ee, U+95f4, U+961f, U+9632, U+9634, U+963b, U+9644-9645, U+964d, U+9664, U+968f, U+9694, U+969c, U+96c6, U+96f6, U+9700, U+9760, U+9762, U+9875-9876, U+9879-987b, U+9884, U+9891, U+9898, U+989c, U+98ce, U+98df, U+9970, U+9996, U+9a71, U+9a8c, U+9ad8, U+9ebb, U+9ec4, U+9ed1, U+9ed8, U+9f50, U+9f99, U+ff08-ff09, U+ff0c, U+ff1a-ff1b, U+ff1f;
}
@font-face {
  font-family: 'Noto Sans SC';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(fonts/notosanssc-500.woff2) format('woff2');
  unicode-range: U+3000-3002, U+300c-300d, U+4e00-4e01, U+4e03, U+4e09-4e0b, U+4e0d-4e0e, U+4e13-4e14, U+4e16, U+4e1a, U+4e1c-4e1d, U+4e22, U+4e24, U+4e2a, U+4e2d, U+4e3a-4e3b, U+4e48-4e49, U+4e4b, U+4e58, U+4e5d, U+4e5f-4e60, U+4e70, U+4e86, U+4e8b-4e8c, U+4e8e, U+4e92, U+4e94, U+4e9a-4e9b, U+4ea4, U+4ea7, U+4eba, U+4ec0, U+4eca-4ecb, U+4ece, U+4ed6, U+4ed8, U+4ee3, U+4ee5, U+4eec, U+4ef6-4ef7, U+4efb, U+4efd, U+4f01, U+4f18, U+4f1a, U+4f20, U+4f30, U+4f38, U+4f3a, U+4f46, U+4f4d-4f4f, U+4f53, U+4f55, U+4f59, U+4f5c, U+4f60, U+4f7f, U+4f8b, U+4f9b, U+4fa7, U+4fbf, U+4fc3, U+4fdd, U+4fee, U+500d, U+5012, U+5019, U+503c, U+504f, U+505a, U+505c, U+50cf, U+5143, U+5145, U+5148, U+5165, U+5168, U+516b-516d, U+5171, U+5173, U+5176-5177, U+517b, U+5185, U+518d, U+5199, U+51b2-51b3, U+51b5, U+51b7, U+51c0, U+51c6, U+51cf, U+51d1, U+51e0, U+51fa, U+5200, U+5206-5207, U+5212, U+5217, U+5219, U+521b, U+522b, U+5230, U+5236-5237, U+524d, U+5269, U+526f, U+529b, U+529e-52a1, U+52a8-52a9, U+5305, U+5316, U+5339-533b, U+5341, U+534a, U+534e, U+5355-5357, U+5360, U+5370, U+5374, U+5377, U+5382, U+538b, U+539a, U+539f, U+53bb, U+53c2, U+53ca, U+53cc-53cd, U+53d1, U+53d6-53d8, U+53e0, U+53e3, U+53e5-53e6, U+53ea, U+53ef-53f0, U+53f3, U+53f7-53f8, U+5403-5404, U+5408, U+540c-540e, U+5411, U+5417, U+5426, U+542b, U+5439, U+544a, U+5458, U+5468, U+547d, U+548c, U+54ac, U+54c1, U+54cd, U+54ea, U+552e, U+5546, U+5668, U+56db, U+56de, U+56e0, U+56e2, U+56f4, U+56fa, U+56fd-56fe, U+5706, U+5728, U+5730, U+573a, U+573e, U+5740, U+5747, U+5750, U+5757, U+5783, U+578b, U+57df, U+57f9-57fa, U+5806, U+5854, U+586b, U+58a8, U+58c1, U+58eb, U+5904, U+5907, U+590d, U+5916, U+591a, U+591f, U+5927, U+5929, U+5934, U+5939, U+5957, U+597d, U+5982, U+59cb, U+59d3, U+5b50, U+5b54, U+5b57-5b58, U+5b66, U+5b83, U+5b88-5b89, U+5b8c, U+5b9a, U+5b9e, U+5ba2, U+5bb6, U+5bb9, U+5bbd, U+5bc4, U+5bf8-5bf9, U+5bfc, U+5c01, U+5c06, U+5c0f, U+5c11, U+5c31, U+5c3a, U+5c3e, U+5c42, U+5c4b, U+5c4f, U+5c71, U+5de1, U+5de5-5de6, U+5dee, U+5df1-5df2, U+5e02, U+5e08, U+5e26, U+5e38, U+5e45, U+5e55, U+5e72-5e74, U+5e76, U+5e7f, U+5e8a, U+5e8f, U+5e94-5e95, U+5e97, U+5e9f, U+5ea6, U+5f00, U+5f02, U+5f04, U+5f0f, U+5f15, U+5f20, U+5f3a, U+5f53, U+5f55, U+5f62, U+5f71, U+5f7c, U+5f80, U+5f84-5f85, U+5f88, U+5f97, U+5fae, U+5fc3, U+5fc5, U+5feb, U+600e, U+601d, U+6027, U+603b, U+60a8, U+60c5, U+60ef, U+60f3, U+610f, U+613f, U+6210-6211, U+6216, U+6237, U+6240-6241, U+624b, U+624d, U+6253, U+625b, U+626f, U+6279, U+627e, U+6280, U+628a, U+6293, U+6295, U+6298, U+62a4-62a5, U+62bb, U+62c5, U+62c9, U+62e2, U+62e5, U+62e9, U+62fc, U+62ff, U+6301, U+6307, U+6309, U+6311, U+6324, U+635f, U+6362, U+6389, U+6392, U+63a5, U+63a7-63a8, U+63d0, U+63d2, U+6491, U+6495, U+64cd, U+652f, U+6536, U+6539, U+653e, U+6545, U+6548, U+6559, U+655e, U+6563, U+6570, U+6574, U+6587, U+6599, U+65a4, U+65ad, U+65b0, U+65b9, U+65cb, U+65e0, U+65e5, U+65f6, U+660e, U+6613, U+6625, U+662f, U+663e, U+66f4, U+6700, U+6709, U+670d, U+671f, U+672a-672c, U+672f, U+673a, U+6742, U+6746, U+6750, U+6761, U+6765, U+677e-677f, U+6784, U+6795, U+679c, U+67b6, U+67d3, U+67e5, U+6807, U+680f, U+6837, U+683c, U+6848, U+684c, U+6863, U+6865, U+68b0, U+68c0, U+695a, U+6a21, U+6a2a, U+6b21, U+6b27, U+6b3e, U+6b62-6b65, U+6b6a, U+6b8a, U+6bb5, U+6bcd, U+6bcf, U+6bd4, U+6beb, U+6c14, U+6c27, U+6c34, U+6c38, U+6c42, U+6c47, U+6c49, U+6c5f, U+6c7d, U+6c9f, U+6ca1, U+6cd5, U+6ce1, U+6ce8, U+6cfd, U+6d3e, U+6d41, U+6d4b, U+6d66, U+6d6a, U+6d6e, U+6d77, U+6df1, U+6df7, U+6dfb, U+6e05, U+6e2f, U+6e32, U+6e38, U+6e7f, U+6e90, U+6ed1, U+7070, U+70b9, U+70e6, U+70ed, U+7136, U+7167, U+7194, U+7247-7248, U+724c, U+7262, U+7269, U+7275, U+7279, U+72b6, U+72ec, U+7387, U+73af-73b0, U+73c0, U+73ed, U+7406, U+7425, U+745e, U+74e3, U+751f, U+7528, U+7531, U+7535, U+753b, U+7545, U+754c, U+7559, U+767d, U+7684, U+76b1, U+76ca, U+76d6, U+76d8, U+76ee, U+76f4, U+76f8, U+7701, U+770b, U+771f, U+773c, U+7740, U+77e5, U+77ed-77ee, U+7801, U+7814, U+7840, U+786c, U+786e, U+793a, U+79bb, U+79cb, U+79cd, U+79d1, U+79f0, U+7a0b, U+7a33, U+7a7a, U+7a7f, U+7a84, U+7a97, U+7acb, U+7ad6, U+7ad9, U+7aef, U+7b14, U+7b26, U+7b2c, U+7b49, U+7b52, U+7b7e, U+7b80, U+7b97, U+7ba1, U+7bad, U+7bb1, U+7c73, U+7c7b, U+7c92, U+7c97-7c98, U+7cbe, U+7cca, U+7cfb, U+7d20, U+7d22, U+7d27, U+7ea2, U+7ea6, U+7eaf, U+7eb8, U+7ebf, U+7ec4, U+7ec6, U+7ec8, U+7ecf, U+7ed3, U+7ed9, U+7edf, U+7eed, U+7ef4, U+7f13, U+7f18, U+7f1d, U+7f29, U+7f6e, U+7ffb, U+8003, U+8005, U+800c, U+8010, U+8015, U+804a, U+8054, U+80cc, U+80fd, U+8109, U+811a, U+819c, U+81c2, U+81ea, U+81f3-81f4, U+820d, U+822a, U+8272, U+827a, U+82a6, U+82b1, U+82cf, U+82f1, U+8303, U+8349, U+8350, U+836f, U+843d, U+846b, U+8584, U+8651, U+8679, U+87ba, U+884c, U+8865, U+8868, U+888b, U+88ab, U+88c1-88c2, U+88c5, U+897f, U+8981, U+8986, U+89c1, U+89c4, U+89c6, U+89c8, U+89d2, U+89e3, U+8a00, U+8ba1-8ba2, U+8ba4, U+8ba9, U+8bad, U+8bb0, U+8bbe-8bbf, U+8bc1, U+8bc4, U+8bc9, U+8bcd, U+8bd5, U+8bdd, U+8be2, U+8be5-8be6, U+8bed, U+8bf4, U+8bfb, U+8c01, U+8c03, U+8d1f, U+8d23, U+8d27-8d28, U+8d2d, U+8d34, U+8d39, U+8d44, U+8d70, U+8d77, U+8d85, U+8d8a, U+8db3, U+8dd1, U+8ddd, U+8ddf, U+8de8, U+8def, U+8df3, U+8eab, U+8f66, U+8f6c, U+8f6e-8f6f, U+8f74, U+8f7b, U+8f83, U+8f85-8f86, U+8fa8, U+8fb9, U+8fbe, U+8fc7, U+8fd0-8fd1, U+8fd8-8fd9, U+8fdb-8fdc, U+8fde, U+8ffd, U+9000-9002, U+9009, U+900f-9010, U+9014, U+901a, U+901f-9020, U+904d, U+9053, U+9063, U+90a3, U+90ae, U+90e8, U+90fd, U+914d, U+9152, U+91c7, U+91cc-91cd, U+91cf, U+9488-9489, U+949f, U+94ae, U+94b1, U+94c1, U+94fe, U+9500, U+9510, U+952e, U+9547, U+957f, U+95e8, U+95ed-95ee, U+95f4, U+961f, U+9632, U+9634, U+963b, U+9644-9645, U+964d, U+9664, U+968f, U+9694, U+969c, U+96c6, U+96f6, U+9700, U+9760, U+9762, U+9875-9876, U+9879-987b, U+9884, U+9891, U+9898, U+989c, U+98ce, U+98df, U+9970, U+9996, U+9a71, U+9a8c, U+9ad8, U+9ebb, U+9ec4, U+9ed1, U+9ed8, U+9f50, U+9f99, U+ff08-ff09, U+ff0c, U+ff1a-ff1b, U+ff1f;
}
@font-face {
  font-family: 'Noto Sans SC';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(fonts/notosanssc-700.woff2) format('woff2');
  unicode-range: U+3000-3002, U+300c-300d, U+4e00-4e01, U+4e03, U+4e09-4e0b, U+4e0d-4e0e, U+4e13-4e14, U+4e16, U+4e1a, U+4e1c-4e1d, U+4e22, U+4e24, U+4e2a, U+4e2d, U+4e3a-4e3b, U+4e48-4e49, U+4e4b, U+4e58, U+4e5d, U+4e5f-4e60, U+4e70, U+4e86, U+4e8b-4e8c, U+4e8e, U+4e92, U+4e94, U+4e9a-4e9b, U+4ea4, U+4ea7, U+4eba, U+4ec0, U+4eca-4ecb, U+4ece, U+4ed6, U+4ed8, U+4ee3, U+4ee5, U+4eec, U+4ef6-4ef7, U+4efb, U+4efd, U+4f01, U+4f18, U+4f1a, U+4f20, U+4f30, U+4f38, U+4f3a, U+4f46, U+4f4d-4f4f, U+4f53, U+4f55, U+4f59, U+4f5c, U+4f60, U+4f7f, U+4f8b, U+4f9b, U+4fa7, U+4fbf, U+4fc3, U+4fdd, U+4fee, U+500d, U+5012, U+5019, U+503c, U+504f, U+505a, U+505c, U+50cf, U+5143, U+5145, U+5148, U+5165, U+5168, U+516b-516d, U+5171, U+5173, U+5176-5177, U+517b, U+5185, U+518d, U+5199, U+51b2-51b3, U+51b5, U+51b7, U+51c0, U+51c6, U+51cf, U+51d1, U+51e0, U+51fa, U+5200, U+5206-5207, U+5212, U+5217, U+5219, U+521b, U+522b, U+5230, U+5236-5237, U+524d, U+5269, U+526f, U+529b, U+529e-52a1, U+52a8-52a9, U+5305, U+5316, U+5339-533b, U+5341, U+534a, U+534e, U+5355-5357, U+5360, U+5370, U+5374, U+5377, U+5382, U+538b, U+539a, U+539f, U+53bb, U+53c2, U+53ca, U+53cc-53cd, U+53d1, U+53d6-53d8, U+53e0, U+53e3, U+53e5-53e6, U+53ea, U+53ef-53f0, U+53f3, U+53f7-53f8, U+5403-5404, U+5408, U+540c-540e, U+5411, U+5417, U+5426, U+542b, U+5439, U+544a, U+5458, U+5468, U+547d, U+548c, U+54ac, U+54c1, U+54cd, U+54ea, U+552e, U+5546, U+5668, U+56db, U+56de, U+56e0, U+56e2, U+56f4, U+56fa, U+56fd-56fe, U+5706, U+5728, U+5730, U+573a, U+573e, U+5740, U+5747, U+5750, U+5757, U+5783, U+578b, U+57df, U+57f9-57fa, U+5806, U+5854, U+586b, U+58a8, U+58c1, U+58eb, U+5904, U+5907, U+590d, U+5916, U+591a, U+591f, U+5927, U+5929, U+5934, U+5939, U+5957, U+597d, U+5982, U+59cb, U+59d3, U+5b50, U+5b54, U+5b57-5b58, U+5b66, U+5b83, U+5b88-5b89, U+5b8c, U+5b9a, U+5b9e, U+5ba2, U+5bb6, U+5bb9, U+5bbd, U+5bc4, U+5bf8-5bf9, U+5bfc, U+5c01, U+5c06, U+5c0f, U+5c11, U+5c31, U+5c3a, U+5c3e, U+5c42, U+5c4b, U+5c4f, U+5c71, U+5de1, U+5de5-5de6, U+5dee, U+5df1-5df2, U+5e02, U+5e08, U+5e26, U+5e38, U+5e45, U+5e55, U+5e72-5e74, U+5e76, U+5e7f, U+5e8a, U+5e8f, U+5e94-5e95, U+5e97, U+5e9f, U+5ea6, U+5f00, U+5f02, U+5f04, U+5f0f, U+5f15, U+5f20, U+5f3a, U+5f53, U+5f55, U+5f62, U+5f71, U+5f7c, U+5f80, U+5f84-5f85, U+5f88, U+5f97, U+5fae, U+5fc3, U+5fc5, U+5feb, U+600e, U+601d, U+6027, U+603b, U+60a8, U+60c5, U+60ef, U+60f3, U+610f, U+613f, U+6210-6211, U+6216, U+6237, U+6240-6241, U+624b, U+624d, U+6253, U+625b, U+626f, U+6279, U+627e, U+6280, U+628a, U+6293, U+6295, U+6298, U+62a4-62a5, U+62bb, U+62c5, U+62c9, U+62e2, U+62e5, U+62e9, U+62fc, U+62ff, U+6301, U+6307, U+6309, U+6311, U+6324, U+635f, U+6362, U+6389, U+6392, U+63a5, U+63a7-63a8, U+63d0, U+63d2, U+6491, U+6495, U+64cd, U+652f, U+6536, U+6539, U+653e, U+6545, U+6548, U+6559, U+655e, U+6563, U+6570, U+6574, U+6587, U+6599, U+65a4, U+65ad, U+65b0, U+65b9, U+65cb, U+65e0, U+65e5, U+65f6, U+660e, U+6613, U+6625, U+662f, U+663e, U+66f4, U+6700, U+6709, U+670d, U+671f, U+672a-672c, U+672f, U+673a, U+6742, U+6746, U+6750, U+6761, U+6765, U+677e-677f, U+6784, U+6795, U+679c, U+67b6, U+67d3, U+67e5, U+6807, U+680f, U+6837, U+683c, U+6848, U+684c, U+6863, U+6865, U+68b0, U+68c0, U+695a, U+6a21, U+6a2a, U+6b21, U+6b27, U+6b3e, U+6b62-6b65, U+6b6a, U+6b8a, U+6bb5, U+6bcd, U+6bcf, U+6bd4, U+6beb, U+6c14, U+6c27, U+6c34, U+6c38, U+6c42, U+6c47, U+6c49, U+6c5f, U+6c7d, U+6c9f, U+6ca1, U+6cd5, U+6ce1, U+6ce8, U+6cfd, U+6d3e, U+6d41, U+6d4b, U+6d66, U+6d6a, U+6d6e, U+6d77, U+6df1, U+6df7, U+6dfb, U+6e05, U+6e2f, U+6e32, U+6e38, U+6e7f, U+6e90, U+6ed1, U+7070, U+70b9, U+70e6, U+70ed, U+7136, U+7167, U+7194, U+7247-7248, U+724c, U+7262, U+7269, U+7275, U+7279, U+72b6, U+72ec, U+7387, U+73af-73b0, U+73c0, U+73ed, U+7406, U+7425, U+745e, U+74e3, U+751f, U+7528, U+7531, U+7535, U+753b, U+7545, U+754c, U+7559, U+767d, U+7684, U+76b1, U+76ca, U+76d6, U+76d8, U+76ee, U+76f4, U+76f8, U+7701, U+770b, U+771f, U+773c, U+7740, U+77e5, U+77ed-77ee, U+7801, U+7814, U+7840, U+786c, U+786e, U+793a, U+79bb, U+79cb, U+79cd, U+79d1, U+79f0, U+7a0b, U+7a33, U+7a7a, U+7a7f, U+7a84, U+7a97, U+7acb, U+7ad6, U+7ad9, U+7aef, U+7b14, U+7b26, U+7b2c, U+7b49, U+7b52, U+7b7e, U+7b80, U+7b97, U+7ba1, U+7bad, U+7bb1, U+7c73, U+7c7b, U+7c92, U+7c97-7c98, U+7cbe, U+7cca, U+7cfb, U+7d20, U+7d22, U+7d27, U+7ea2, U+7ea6, U+7eaf, U+7eb8, U+7ebf, U+7ec4, U+7ec6, U+7ec8, U+7ecf, U+7ed3, U+7ed9, U+7edf, U+7eed, U+7ef4, U+7f13, U+7f18, U+7f1d, U+7f29, U+7f6e, U+7ffb, U+8003, U+8005, U+800c, U+8010, U+8015, U+804a, U+8054, U+80cc, U+80fd, U+8109, U+811a, U+819c, U+81c2, U+81ea, U+81f3-81f4, U+820d, U+822a, U+8272, U+827a, U+82a6, U+82b1, U+82cf, U+82f1, U+8303, U+8349, U+8350, U+836f, U+843d, U+846b, U+8584, U+8651, U+8679, U+87ba, U+884c, U+8865, U+8868, U+888b, U+88ab, U+88c1-88c2, U+88c5, U+897f, U+8981, U+8986, U+89c1, U+89c4, U+89c6, U+89c8, U+89d2, U+89e3, U+8a00, U+8ba1-8ba2, U+8ba4, U+8ba9, U+8bad, U+8bb0, U+8bbe-8bbf, U+8bc1, U+8bc4, U+8bc9, U+8bcd, U+8bd5, U+8bdd, U+8be2, U+8be5-8be6, U+8bed, U+8bf4, U+8bfb, U+8c01, U+8c03, U+8d1f, U+8d23, U+8d27-8d28, U+8d2d, U+8d34, U+8d39, U+8d44, U+8d70, U+8d77, U+8d85, U+8d8a, U+8db3, U+8dd1, U+8ddd, U+8ddf, U+8de8, U+8def, U+8df3, U+8eab, U+8f66, U+8f6c, U+8f6e-8f6f, U+8f74, U+8f7b, U+8f83, U+8f85-8f86, U+8fa8, U+8fb9, U+8fbe, U+8fc7, U+8fd0-8fd1, U+8fd8-8fd9, U+8fdb-8fdc, U+8fde, U+8ffd, U+9000-9002, U+9009, U+900f-9010, U+9014, U+901a, U+901f-9020, U+904d, U+9053, U+9063, U+90a3, U+90ae, U+90e8, U+90fd, U+914d, U+9152, U+91c7, U+91cc-91cd, U+91cf, U+9488-9489, U+949f, U+94ae, U+94b1, U+94c1, U+94fe, U+9500, U+9510, U+952e, U+9547, U+957f, U+95e8, U+95ed-95ee, U+95f4, U+961f, U+9632, U+9634, U+963b, U+9644-9645, U+964d, U+9664, U+968f, U+9694, U+969c, U+96c6, U+96f6, U+9700, U+9760, U+9762, U+9875-9876, U+9879-987b, U+9884, U+9891, U+9898, U+989c, U+98ce, U+98df, U+9970, U+9996, U+9a71, U+9a8c, U+9ad8, U+9ebb, U+9ec4, U+9ed1, U+9ed8, U+9f50, U+9f99, U+ff08-ff09, U+ff0c, U+ff1a-ff1b, U+ff1f;
}
/* ============================================================
   TOKENS
   ============================================================ */
:root{
  --canvas:#0B0F14;
  --panel:#141B24;
  --panel-2:#1B242F;
  --line:#232E3B;
  --accent:#FFC629;
  --accent-hover:#FFD558;
  --accent-muted:rgba(255,198,41,.22);
  --glow:#BFE3F2;
  /* the sealing bars, and the only warm colour on the site that is not the
     accent -- it reads as heat, which is what it is */
  --heat:#FF7A3D;
  --text-primary:#EEF3F7;
  /* Raised on 2026-08-30. #93A3B3 cleared 7.4:1 on the canvas, which passes,
     but a cool low-saturation grey at body size reads hazy rather than crisp
     and the customer said it was tiring. #D3D9DF is 13.5:1. --text-dim moves
     with it, or the two collapse into one another. Chosen off
     review/build-preview-ink.js, four options compared on the real page. */
  --text-secondary:#D3D9DF;
  --text-dim:#A6AEB6;

  /* Lato across the whole site -- headings, body, nav, buttons, cards, Q&A.
     Two things deliberately do NOT follow it.

     The wordmark keeps Archivo: a trademark does not change typeface because
     the body copy did, and REIZE TECH / 锐泽科技 is a solved two-line lockup
     whose widths were matched glyph by glyph (see .bs below). It gets its own
     variable so that changing --display again never silently moves it.

     The model numbers keep IBM Plex MONO: RZ-800 and its siblings sit in a
     spec table and in the mega menu, where the digits have to line up in a
     column. A proportional face breaks that alignment.

     Lato is a static face, not a variable one, so the 'wdth' axis settings
     scattered through this stylesheet simply do not apply to it any more --
     they are inert rather than wrong, and they still drive the wordmark. */
  --display:'Lato','IBM Plex Sans','Noto Sans SC',system-ui,sans-serif;
  --body:'Lato','IBM Plex Sans','Noto Sans SC',system-ui,sans-serif;
  --mono:'IBM Plex Mono',ui-monospace,monospace;
  --wordmark:'Archivo','Noto Sans SC',system-ui,sans-serif;
  /* 大标题留在 Archivo。Lato 是静态字体，没有宽度轴，所以站上给标题写的
     font-variation-settings:'wdth' 112 对它完全无效——量下来同一句话从
     1052.9px 缩到 882.4px，窄了 16.2%。想用 Lato 补回这个宽度，只能把字号
     放大到 69.2px，那是把字变大，不是变宽，标题块还会高 35px。
     所以只有大标题用 Archivo，正文、导航、按钮、卡片、Q&A 仍然是 Lato。 */
  --headline:'Archivo','IBM Plex Sans','Noto Sans SC',system-ui,sans-serif;

  --ease:cubic-bezier(.22,.61,.36,1);
  --ease-out:cubic-bezier(.16,1,.3,1);

  /* One size for every small label on the site, and one for the ordinals that
     number a sequence. Both were hard-coded at 11px in nine different rules,
     which is how they ended up looking small and unconsidered -- there was no
     single place to make them bigger. */
  /* Links, language switch and the call-to-action all read from this, so the
     bar is one set of controls at one size rather than three sizes in a row. */
  /* The mark's size, in one place. Three rules derive from it -- the SVG, the
     footer's negative offset (6/24 of it), and the phone overrides -- and having
     the number spelled out in each was an invitation to change one and miss the
     others, which is exactly what happened the first time the bar was shortened. */
  --mark:75.9px;   /* the lockup, 15% up from 66 */
  --navsize:clamp(15px,1.35vw,19px);
  --label:clamp(14px,1.15vw,17px);
  --ordinal:clamp(15.5px,1.4vw,19.5px);
  --tshadow:0 1px 2px rgba(5,5,10,.95),0 3px 12px rgba(5,5,10,.78),0 10px 44px rgba(5,5,10,.8);

  --gut:clamp(20px,5vw,72px);
  --maxw:1320px;

  /* One device pixel, expressed in CSS px. mega.js overwrites it on <html>
     with 1/devicePixelRatio -- 0.8px at 125% Windows scaling, which is what
     most Windows laptops run. Vertical measurements in the 设备 menu are
     rounded to multiples of it so every hairline lands on the screen's own
     grid.

     Rounding to whole CSS pixels was not enough: 22px x 1.25 is 27.5 device
     pixels, and every row below inherits that half, which is why one row's
     yellow bar rendered 75/50 and the next 25/100 -- the same ink, spread
     differently, so one looked thinner.

     IT HAS TO LIVE HERE, not on .mmpanel. A custom property declared on the
     panel shadows the inline value JavaScript sets on <html>, so the panel
     would read 1px for ever and the rounding would do nothing. That is exactly
     what happened the first time. */
  --dp:1px;
}

*,*::before,*::after{box-sizing:border-box}
html,body{overflow-x:hidden;overflow-x:clip}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--canvas);color:var(--text-primary);
  font-family:var(--body);font-size:clamp(15px,1.05vw,17px);line-height:1.65;
  -webkit-font-smoothing:antialiased;
}

/* bilingual: one language visible at a time, works with JS off */
html[data-lang="en"] [lang]:not([lang="en"]){display:none !important}
html[data-lang="zh"] [lang]:not([lang="zh"]){display:none !important}
html[data-lang="ru"] [lang]:not([lang="ru"]){display:none !important}
html[data-lang="pt"] [lang]:not([lang="pt"]){display:none !important}
html[data-lang="es"] [lang]:not([lang="es"]){display:none !important}
html[data-lang="zh"] body{font-family:var(--body)}

.skip{position:absolute;left:-9999px;top:0;z-index:200;background:var(--accent);color:#0B0F14;
  padding:12px 20px;border-radius:0 0 8px 0;font-weight:700}
.skip:focus{left:0}

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

.wrap{max-width:var(--maxw);margin:0 auto;padding-inline:var(--gut)}

/* ============================================================
   FIXED ENVIRONMENT LAYER: the page is one place, not stacked boxes
   ============================================================ */
.env{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden}
.env::before{
  content:"";position:absolute;inset:-25%;
  /* 两团径向光晕（左上淡蓝 .055、右下琥珀 .045）已去掉。
     这一层是 position:fixed、z-index:0，压在所有 .sec 底下。只有 .sec.photo 有
     不透明底色，会把它挡住；#converge / #faq / #contact 都没有背景，于是它就透
     出来了。又因为是固定层，不随内容滚动，看上去就是一块停在屏幕上的亮斑，页面
     从它底下滑过去 —— 客户的描述正是「滚到上方没有，逐渐下滚才有」。
     这也是为什么不能靠「让 .sec.photo 也透光」来解决：那只会把亮斑铺满整页。
     元素本身留着不删，它还占着 z-index:0 这一层的位置。 */
  /* The drift animation was removed. It scaled this layer from 1 to 1.09 over
     82 seconds, and a scaling composited layer is re-rastered as it scales --
     fixed position, larger than the viewport, forever, underneath every other
     thing on the page. Compared directly with it stopped, stopped is smoother,
     and an 82-second 9% scale of a faint gradient is not something anyone was
     going to see. If it is ever wanted back, bring back the translate and NOT
     the scale: translating a layer costs no raster. */
}

/* ============================================================
   NAV
   ============================================================ */
.nav{
  position:fixed;top:0;left:0;right:0;z-index:60;
  display:flex;align-items:center;gap:clamp(16px,3vw,44px);
  padding:4px var(--gut);
  transition:background .5s var(--ease),border-color .5s var(--ease);
  border-bottom:1px solid transparent;
}
/* No backdrop-filter. It re-blurred the full width of the bar every frame
   against whatever was passing underneath, and during the transition between
   the hero and page two that is a screen of photograph moving nearly a
   thousand pixels. Compared side by side against the blurred version, this is
   the one that holds its frame rate. Opaque rather than translucent, because
   translucent without the blur just looks like a mistake. */
/* 下边框保持透明。它原来在实心态染成 var(--line)（导航里这个变量是 #232E3B），
   压在 #0B0F14 的导航底上就是一条隐隐约约的线，客户要求去掉。
   边框本身留着不删：它占 1px 高度，删掉导航会矮 1px，而下拉面板的高度上限是
   mega.js 按导航实测高度算的（--navh），改高度就要牵动它。 */
.nav.solid{background:#0B0F14;border-bottom-color:transparent}
/* NOTE: the nav's blur was once suspended during transitions (html.gliding,
   backdrop-filter:none) to save per-frame GPU time. It stuttered worse. Adding
   and removing backdrop-filter creates and destroys a compositing layer, so the
   change costs a re-raster at the start of the move and another at the end --
   two guaranteed hitches, bought to save frames in between that were not
   dropping anyway. Leave the blur alone. */
/* fit-content matters in the footer: there the lockup is a block, so without
   it the anchor stretches to the whole grid column and anything measured
   against it (the strapline width) comes out nearly twice too wide. */
.brand{display:flex;align-items:center;gap:calc(var(--mark) * 4 / 66);text-decoration:none;flex:0 0 auto;width:fit-content}
/* the mark's drawn shape spans 20 of the 24 viewBox units, so the box is sized
   about 1.2x the height the mark should read at, which is the height of the
   two lines of type beside it */
/* 84px box, so the mark draws 70px -- its path spans 20 of the 24 viewBox units.
   The type block beside it is deliberately shorter: the two lines are locked
   tight to each other so they read as one thing, and the mark standing a little
   taller than that block is the normal relationship in a combination mark. */
.brand svg{width:var(--mark);height:var(--mark);display:block;color:var(--accent);flex:0 0 auto}
.brand .bt{display:flex;flex-direction:column;line-height:1}
/* The trailing letter-space has to come off this line too. Tracking adds a gap
   after the LAST glyph as well, so a line that only looks as wide as its ink
   actually measures wider -- and solving the Chinese line against that inflated
   number left the two edges about 10px apart on screen while the maths said
   they matched. Both lines now end where their ink ends. */
/* Tracking pulled in from .18em: the line was reading wider than the mark was
   tall, which is what made the three parts look like three parts. */
/* .12em was tuned for a seven-glyph wordmark. REIZE TECH is ten plus a space,
   and at that tracking its ink ran 144px against the Chinese line's natural
   73px -- which forced 23.6px between the four Chinese characters just to make
   the two lines the same width. Pulling the Latin in is the right end to fix
   it from: a ten-letter wordmark does not need the air a seven-letter one did. */
/* 方案六（2026-09-10 定稿）：REIZE 一行，MACHINERY 一行，字标里没有汉字。

   为什么不是把 TECH 直接换成 MACHINE —— 下面那条老注释描述的规则是「四个汉字
   拉开到跟拉丁一行同宽」。拉丁从 10 个字符变 13 个，同一条规则解出来字距是
   33.5px，而老注释里已经写明 23.6px 就是「读成四个字，不是一个词」的上限。
   两行都是拉丁之后，同宽这件事重新变得轻松：五个字母对九个字母，第二行
   拉开 1.6px 就够到了，比站上任何一版历史字距都小。

   两行同一个字体（--wordmark，也就是 Archivo）。一个两行的纯拉丁字标混进
   正文字体是看得出来的 —— 方案四那一版第二行有汉字，混排才成立。

   nowrap 是必须的：.bt 是 flex 列，导航挤起来的时候第二行会断成两行，
   字标就凭空多出一行高。在预览页上撞到过一次。

   代价，写在这儿好过以后再发现：中文名不在字标里了，全站只剩页面标题、
   首屏那行小字和页脚版权行还写着它。那三处要跟着公司改名一起维护。 */
/* 加宽（2026-09-10 定稿）：客户嫌字标左右太短。
   两条不变形的路一起用 —— 宽度轴推宽，再加一点字距。
   宽度轴是字体设计师按每个宽度画好的字形，不是把字横向抻开，
   所以笔画粗细没有被改。scaleX 那条路比过了，竖笔会变粗，没选。

   轴取 118 不是上限 125：先上过一版 125 + .15em，字标 171.8px，
   客户看过之后收到这一档 —— 160.5px，比原来的 140.9px 宽 19.6px。
   Archivo 这份文件的轴范围是 build-fonts.js 抓的时候定的
   （wdth,wght@100..125,600..700），118 在里面，不用重抓字体。 */
.brand .bn{font-family:var(--wordmark);font-weight:700;font-size:calc(var(--mark) * 26 / 66);
  font-variation-settings:'wdth' 118;
  letter-spacing:.09em;margin-right:-.09em;
  color:var(--text-primary);white-space:nowrap}
/* the Chinese line is tracked out to sit under REIZE TECH at the same width.
   RE-SOLVED for REIZE TECH (2026-08-31). Seven glyphs became ten plus a
   space, so the Latin line got much wider and the four Chinese characters had
   to be tracked much further apart to keep up. Six proportions were compared
   on a switch (review/build-preview-logo.js) and the customer chose this one:
   Latin 18/66 at .03em, Chinese 17/66, both lines 133.1px of ink, tracking
   18.3px. The mark was not touched -- it stays 76px in all of them.

   The tension worth knowing about: a larger Latin line and a smaller Chinese
   one pull AGAINST equal width, because four characters have to span whatever
   ten letters span. One step further out took the tracking to 23.6px, which
   reads as four separate characters rather than a word.
   the negative margin removes the trailing letter-space so both lines start and end together.
   the tracking value is solved by review/measure-brand.js, not guessed. */
/* Measured, not guessed: the mark draws 58.3px tall (its path spans 20 of 24
   viewBox units), and the two lines of type had to reach the same. The gap
   between them was 13px of ink-to-ink, which read as two separate lines beside
   one compact mark; pulling it to about 8 and taking the sizes up together
   makes the pair one block that stands exactly as tall as the mark. */
/* 9px between the lines, and the tracking solved so both end on the same ink
   edge -- solved by review/measure-brand.js against INK width, not laid-out
   width, which are ten pixels apart when both lines carry heavy tracking. */
/* 700, chosen off review/build-preview-bs.js. Note it must be 700 and not
   600: the site fetches Noto Sans SC at 400 and 700 only, and CSS font
   matching sends anything between them down to 400 -- which is why this line
   declared 500 for months and rendered 400 the whole time. */
/* 字距 .25em：解出来的，让 MACHINERY 的墨宽正好等于 REIZE 的 95.8px。
   解算器给的是 .2499em，八个字距加起来差 0.009px，远在一个设备像素以下，
   所以写成 .25em —— 好读，也看得出是定过的值，不是凑出来的。
   写成 em 而不是死像素：em 跟着字号走，字号又是 --mark 的分数，
   所以断点上 --mark 变小，两行仍然同宽。第一行的 .09em 是同一个道理。
   负 margin 收掉最后一个字母后面那段看不见的字距 —— 不收的话两行的右边缘
   在屏幕上差着一整个字距，而算出来的数字是对齐的。
   两行的宽度轴要一起给：只给第一行的话，MACHINERY 还是 100 的宽度，
   解出来虽然还是同宽，但两行的字形明显不是一套。 */
.brand .bs{font-family:var(--wordmark);font-weight:700;font-size:calc(var(--mark) * 11 / 66);
  font-variation-settings:'wdth' 118;
  letter-spacing:.25em;margin-right:-.25em;
  color:var(--text-secondary);margin-top:calc(var(--mark) * 7.1 / 66);
  align-self:flex-start;white-space:nowrap}

.navlinks{display:flex;gap:clamp(10px,1.33vw,21px);margin-left:auto;align-items:center;
  flex-wrap:nowrap;min-width:0}
/* :not(.btn) matters. A bare `.navlinks a` is more specific than `.btn`, so it was
   silently overriding the call-to-action's colour, its horizontal padding and its
   weight: the button rendered grey with the label touching both edges. */
.navlinks a:not(.btn){
  color:var(--text-secondary);text-decoration:none;
  font-family:var(--display);font-weight:600;font-size:var(--navsize);letter-spacing:.015em;
  font-variation-settings:'wdth' 104;flex:0 0 auto;
  padding:6px 0;position:relative;transition:color .35s var(--ease);white-space:nowrap;
}
/* CJK glyphs read smaller than Latin at the same nominal size, so the bar steps
   up as a whole. Derived from --navsize, never hard-coded: the previous three
   fixed values had drifted apart to 16px, 15.36px and 15.5px sitting in one row. */
html[data-lang="zh"] .navlinks a:not(.btn),
html[data-lang="zh"] .langsw,
html[data-lang="zh"] .navlinks .btn{font-size:calc(var(--navsize) * 1.04)}
html[data-lang="zh"] .navlinks a:not(.btn){font-family:var(--body);font-weight:500;letter-spacing:.06em}
html[data-lang="zh"] .langsw{font-family:var(--body);font-weight:500;letter-spacing:.06em}
.navlinks a:not(.btn)::after{
  content:"";position:absolute;left:0;right:100%;bottom:0;height:1px;background:var(--accent);
  transition:right .4s var(--ease);
}
.navlinks a:not(.btn):hover{color:var(--text-primary)}
.navlinks a:not(.btn):hover::after{right:0}

/* Same face, same size, same weight as the links beside it. It used to be mono
   at 11.5px next to a 15.5px display face -- two typefaces at two sizes sitting
   in one row, which is exactly why the bar did not read as one set of controls. */
.langsw{display:flex;align-items:center;gap:8px;
  font-family:var(--display);font-weight:600;font-size:var(--navsize);
  letter-spacing:.015em;flex:0 0 auto}
.langsw button{
  background:none;border:0;padding:5px 4px;cursor:pointer;color:var(--text-dim);
  font:inherit;white-space:nowrap;transition:color .3s var(--ease);
}
.langsw button[aria-pressed="true"]{color:var(--accent)}
.langsw span{color:var(--line)}

.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:9px;
  /* the plate sits close to the words rather than holding them at arm's length */
  padding:7px 12px;border-radius:2px;text-decoration:none;
  font-family:var(--display);font-weight:700;font-size:var(--navsize);font-variation-settings:'wdth' 104;
  letter-spacing:.02em;border:1px solid transparent;cursor:pointer;white-space:nowrap;
  flex:0 0 auto;   /* a flex parent must never squeeze a nowrap button narrower than its label */
  transition:background .35s var(--ease),color .35s var(--ease),border-color .35s var(--ease),transform .35s var(--ease);
}
html[data-lang="zh"] .btn{font-family:var(--body);font-weight:700;font-size:15.5px;letter-spacing:.08em}
/* Outlined everywhere, not only inside the paired control. A lone filled block
   of accent on a dark panel is the same construction the paired version was
   rebuilt to avoid -- leaving the form's submit as a solid slab while every
   other action was a hairline would have been inconsistent as well as
   reintroducing it. The accent carries the emphasis as text and as the border. */
.btn-primary{background:none;color:var(--accent);
  border-color:rgba(255,198,41,.42);text-shadow:none}
.btn-primary:hover{background:rgba(255,198,41,.10);
  border-color:rgba(255,198,41,.68);transform:none}
/* the bar's action inherits the outlined treatment; it only needs the tighter
   radius that suits its smaller size */
.navlinks .btn-primary{border-radius:3px}
.btn-ghost{border-color:var(--line);color:var(--text-primary)}
.btn-ghost:hover{border-color:var(--accent);color:var(--accent)}

/* 服务那一节已经搬去 service.html 了——跟工厂页一样，导航的落点应该是一个页面，
   不是别人滚动条里第四屏的一个锚点。原来钉在那一节上的上边距规则连同它的排查
   记录一起删掉了，那个 id 现在整站都不存在。

   记录里有一条对剩下四个 .paged 区块仍然成立，留在这里：整屏高的区块是
   display:flex 加 align-items:center，内容由居中决定位置，padding-top 根本不
   参与。想挪动这类区块里的内容，调 padding 是没有用的。 */

/* ===================== the 设备 mega menu ==================================
   A card hanging under the bar, not a band across the page. Its right edge
   lines up with the bar's own right edge -- the same --gut the quote button
   ends on -- so the panel belongs to the row of links it drops from.

   The white is #EEF3F7, which is the white of the words on the first screen,
   and the type is black.

   Alignment is structural, not tuned. The headings and the lists are two grids
   sharing one set of columns, with a single hairline running between them
   across the whole card; a rule per column can only ever be as straight as the
   copy happens to be. The heading and the machine names below it start on the
   same pixel because the hover plate bleeds left by exactly the padding it
   adds, so the text edge never moves.

   The panel is a child of .nav, NOT of .navlinks. `.navlinks a:not(.btn)` is
   specific enough to have captured every link in here -- colour, display face,
   the sliding underline, white-space:nowrap -- which is the same cascade
   collision that once turned the call-to-action grey. Keeping it outside the
   container means no override rules are needed at all.

   No backdrop-filter and no will-change. The bar was rebuilt once to get rid of
   the first, and the second is what made the hero expensive to composite.
   Closed, the panel is visibility:hidden, so it costs nothing until it is used.
   ------------------------------------------------------------------------- */
.mmpanel{
  /* Plain white, one tone across the whole card -- no shadow inside it, no
     gradient, nothing but the colour.

     It was #EEF3F7 before, which is the exact value of the words on the first
     screen (measured identical on both). But that value is a blue-tinted white:
     as thin letterforms on a near-black ground it reads white, and spread over
     a panel this size it reads grey-blue, which is what the customer saw. The
     only way for the field to LOOK like that white is for it to be white. */
  --paper:#FFFFFF;
  --wash:rgba(14,20,27,.055);
  /* Neutral greys, not the site's blue-greys. The background was already
     rgb(255,255,255) -- measured on our own render AND on the reference site,
     identical -- but every line and every secondary colour drawn on it carried
     a blue cast, and a white field seen through blue-grey lines reads cool.
     These are the reference's own values: the dashed rule is exactly its
     #D2D2D2, and the ink is its #111. */
  /* ONE colour for both rules. The line under a series heading and the line
     under a machine differ in one thing only -- solid against dashed -- so they
     share a token; two near-identical greys were drifting apart every time one
     of them was adjusted. Darker than the reference's #D2D2D2, which the
     customer found too faint to see. */
  --mline:#BDBDBD;
  /* the reference sets its dropdown items in #111; matched exactly */
  --ink:#111111;
  --ink-dim:#767676;
  /* The panel used Lato for a while, copied off a reference site. It was the
     only place on the whole site running a face the rest of the pages do not
     use, so the menu read as a different product from the page behind it.
     Now it uses the site's own two: the display face for the series headings,
     the body face for the machine names -- the same pairing the cards and the
     page headings use. Noto Sans SC still catches the Chinese. */
  --menu:var(--display),'Noto Sans SC',sans-serif;
  --menu-body:var(--body),'Noto Sans SC',sans-serif;
  --mmpad:clamp(18px,1.5vw,26px);
  /* the four series stand further apart than the rows inside them do, so the
     card reads as four things rather than one field of links */
  --mmgap:clamp(30px,3.4vw,64px);
  /* Our accent is #FFC629, which as text on white is illegible. This is that
     amber taken dark enough to read -- 5.9:1 -- so the hover is still the
     brand rather than an arbitrary new colour. The reference does the same
     thing with its own green, #1d5837. */
  --hot:#8A5A00;

  /* ---- the pixel grid ------------------------------------------------
     A 1px line drawn at a fractional y is antialiased across two device pixel
     rows at half strength each, and reads thinner than the same line drawn on
     a whole pixel. Measured: the bar under A-800 landed at y .578 (split)
     while R-800 landed at .047 (crisp) -- which is exactly the pair that was
     reported as different thicknesses.

     The drift came from every vertical measurement being fractional: the bar
     is 84.891px tall (derived from --mark), .mmin's padding was 21.6px from a
     vw clamp, and the rows were 62.47px from a unitless line-height. Rounding
     each one to a whole pixel puts every bar on the grid, so they all render
     the same. The plain value is declared first as the fallback. */
  position:absolute;top:100%;top:round(100%, var(--dp));left:0;right:0;
  max-height:calc(100vh - var(--navh,85px));
  display:flex;flex-direction:column;
  background:var(--paper);
  border-bottom:1px solid var(--accent);
  border-radius:0 0 3px 3px;
  box-shadow:0 22px 54px rgba(5,9,14,.50);
  opacity:0;visibility:hidden;transform:translateY(-10px);
  transition:opacity .24s var(--ease),transform .24s var(--ease),visibility 0s .24s;
  text-align:left;
}
.nav[data-mm="open"] .mmpanel{
  opacity:1;visibility:visible;transform:none;
  transition:opacity .28s var(--ease-out),transform .34s var(--ease-out),visibility 0s;
}
/* The pointer crosses the bar's own padding on its way down to the panel, so
   something has to catch it there or the menu shuts under the mouse.

   IT MUST NOT BE FULL WIDTH. It was -- left:0 right:0 on the panel -- which put
   an invisible strip across the whole bottom of the bar, so sliding sideways
   from the lower half of 设备 towards 服务 kept re-entering the panel: the menu
   reopened, the trigger leave closed it, and it flickered without ever settling.
   The bridge belongs to the TRIGGER, and is only as wide as it. */
#mmtrig::before{content:"";position:absolute;left:-14px;right:-14px;top:100%;height:34px}

/* the card is full width; the CONTENT sits on the page's own column, so the
   menu lines up with every other section of the site rather than with itself */
.mmin{max-width:var(--maxw);margin:0 auto;
  padding:var(--mmpad) var(--gut) calc(var(--mmpad) - 5px);
  padding-top:round(var(--mmpad), var(--dp));
  padding-bottom:round(calc(var(--mmpad) - 5px), var(--dp));
  overflow-y:auto;overscroll-behavior:contain;min-height:0;width:100%}

/* Four equal columns. Equal columns went in, came out -- with ragged text the
   gaps between the four families read as 150px, 39px and 43px -- and went back
   in, because the RULES are what fix that. A rule under each heading and a
   dashed rule under each machine draw the column edges, so the eye measures the
   distance between rules, which is regular, instead of between ragged text.
   Every dashed rule is therefore the same length, which is the point. */
.mmin{display:grid;grid-template-columns:repeat(4,1fr);column-gap:var(--mmgap)}

/* one typeface through the whole panel, headings and items alike */
.mmin > h3{grid-row:1;margin:0;padding:0 0 10px;padding-bottom:round(10px, var(--dp));
  border-bottom:1px solid var(--mline);border-bottom-width:round(1px, var(--dp));
  font-family:var(--menu);font-weight:700;font-size:calc(var(--label) * 1.34);
  letter-spacing:.005em;color:var(--ink);line-height:1.2;line-height:round(1.2em, var(--dp))}
html[data-lang="zh"] .mmin > h3{letter-spacing:.03em}

.mmin > ul{grid-row:2;list-style:none;margin:0;padding:0;min-width:0}
.mmin li{margin:0}
/* the row is the full width of the column, so its dashed rule is too */
.mmin li a{position:relative;display:flex;align-items:center;justify-content:space-between;gap:10px;
  text-decoration:none;color:var(--ink);
  font-family:var(--menu-body);font-weight:400;font-size:calc(var(--label) * .95);
  line-height:1.35;line-height:round(1.35em, var(--dp));
  padding:9px 0;padding-block:round(9px, var(--dp));
  border-bottom:1px dashed var(--mline);border-bottom-width:round(1px, var(--dp));
  transition:color .18s var(--ease),border-bottom-color .18s var(--ease)}
html[data-lang="zh"] .mmin li a{font-weight:500}
.mmin li a > span[lang]{min-width:0}
/* The dashed rule is structure and stays put. What lights up is a solid
   underline directly beneath the words, which is what the reference does
   (text-decoration:underline plus its brand colour) and is far easier to see
   than a change of shade in a hairline. The designation is not underlined --
   the reference leaves its own code out of the underline too. */
/* A solid logo-yellow bar under the row, the same length as the dashed rule.

   Three earlier attempts are why this is worth a note. text-decoration draws one
   underline PER LINE of text, so a wrapped row lit up as two segments. Moving it
   to the row's own border-bottom made it one line, but put it down ON the
   dashed rule instead of under the words. Colouring the text yellow instead
   avoided both, but #FFC629 on white is 1.8:1 and looked wrong.

   This is an absolutely positioned bar on the ROW: one piece however the text
   wraps, exactly as long as the dashed rule beneath it, and floating clear of
   it. The words stay #111; the chevron carries the yellow with the bar.

   Logo yellow, as asked. The words stay #111: #FFC629 as text on white is
   1.8:1 and would be harder to read hovered than not, so the yellow does the
   highlighting as the bar and the chevron, not as the text. */
/* the bar is as long as the dashed rule below it -- left:0 right:0 on the row,
   not on the text -- and floats between the words and that rule rather than
   sitting on it. Absolutely positioned, so it is not a flex item and adds no
   height: the row does not move when it appears. */
.mmin li a:hover::before{content:"";position:absolute;left:0;right:0;
  /* --bar-adj is set per row by mega.js: the distance, in CSS px, that moves
     this row's bar onto a whole device pixel. Zero when it is already there,
     and always zero at devicePixelRatio 1. */
  bottom:calc(3px + var(--bar-adj, 0px));height:round(1px, var(--dp));background:var(--accent)}
.mmin li a:hover::after{opacity:1;border-color:var(--accent);transform:rotate(45deg) translate(2px,-2px)}
.mmin li a:focus-visible{outline:2px solid var(--ink);outline-offset:2px}

/* the chevron from the reference, drawn rather than fetched */
/* 描边 2.5px 改成 1.5px，跟 Q&A 那颗箭头（.faq summary::after）一致 —— 客户要求。
   上面那条旧记录说「6px 配 1.5px 会消失」，那是尺寸只有 6px 时的事；现在是 9px，
   1.5px 在这个尺寸上站得住。真正要盯的是 opacity:.55：Q&A 那颗是全不透明的，
   这颗打了五五折，细了之后就靠它决定读不读得出来。 */
.mmin li a::after{content:"";flex:0 0 auto;width:9px;height:9px;margin-right:2px;
  border-right:1.5px solid currentColor;border-top:1.5px solid currentColor;
  /* 静止态不再打折。原来是 opacity:.55 —— 颜色一直是 currentColor，发灰的是这层
     半透明；描边从 2.5px 收到 1.5px 之后它更明显了。现在跟旁边的机型名同一个黑。 */
  transform:rotate(45deg);opacity:1;
  transition:opacity .18s var(--ease),transform .18s var(--ease)}
/* 悬停也保持不打折：静止已经是 1，如果这里还留 .9，鼠标移上去反而会变淡。
   变色（border-color:var(--accent)）由上面那条 :hover 规则负责，不受影响。 */
.mmin li a:hover::after{opacity:1;transform:rotate(45deg) translate(2px,-2px)}

/* the designation runs on from the name rather than sitting in a column of its
   own -- it is part of what the machine is called. 1em, so it is the same size
   as the name and follows it wherever that changes. */
.mmmodel{font-family:var(--mono);font-weight:500;font-size:1em;
  letter-spacing:.02em;color:var(--ink-dim);margin-left:.5em;white-space:nowrap}

.mmfoot{flex:0 0 auto;border-top:1px solid var(--mline);background:var(--paper)}
.mmfootin{max-width:var(--maxw);margin:0 auto;padding:10px var(--gut);
  display:flex;align-items:center;justify-content:space-between;gap:16px}
.mmfootin p{margin:0;font-family:var(--menu-body);font-size:calc(var(--label) * .96);
  line-height:1.4;color:var(--ink-dim)}

.mmfootin a{flex:0 0 auto;text-decoration:none;color:var(--ink);
  font-family:var(--menu);font-weight:700;font-size:calc(var(--label) * 1.02);
  letter-spacing:.02em;white-space:nowrap;border-bottom:1px solid var(--accent);padding-bottom:2px}
html[data-lang="zh"] .mmfootin a{letter-spacing:.04em}
.mmfootin a:hover{border-bottom-color:var(--ink)}

/* the trigger keeps its underline lit while the panel is down, so the bar shows
   which item the panel belongs to */
.nav[data-mm="open"] #mmtrig{color:var(--text-primary)}
.nav[data-mm="open"] #mmtrig::after{right:0}

/* four columns need about 230px each before the names start breaking up */
@media (max-width:1000px){
  .mmin{grid-template-columns:repeat(2,auto)}
  .mmin > h3:nth-of-type(3){grid-row:4}
  .mmin > h3:nth-of-type(4){grid-row:4}
  .mmin > ul:nth-of-type(3),.mmin > ul:nth-of-type(4){grid-row:5}
  .mmin > h3:nth-of-type(3),.mmin > h3:nth-of-type(4){padding-top:18px}
}
/* On a touch screen there is no hover to open it with, and a tap should take
   you to the listing page rather than fight the panel for the same gesture. */
@media (pointer:coarse),(max-width:820px){ .mmpanel{display:none} }
@media (prefers-reduced-motion:reduce){
  .mmpanel{transition:opacity .01s,visibility 0s .01s;transform:none}
  .nav[data-mm="open"] .mmpanel{transition:opacity .01s,visibility 0s;transform:none}
}
@media (pointer:coarse) and (max-width:820px){
  .btn{min-height:44px;display:inline-flex;align-items:center;justify-content:center}
  /* the nav plate is exempt: 44px of thumb target there is what made it tall */
  .navlinks .btn{min-height:0}
}

/* ============================================================
   HERO
   ============================================================ */
/* 130vh: the sticky stage plus 30vh of scroll, about three notches of a mouse wheel.
   Far shorter than the 80 to 130vh per-band plateau the pacing standard asks for.
   Two things make that work rather than break it: the first beat is already
   assembled when the page loads, so it is read at rest rather than in passing, and
   the second is terminal. The camera move was slowed to 1.12x to match. Shortening
   the scroll without slowing the move would have read as faster, not smoother. */
.hero{position:relative;height:130vh;z-index:1}
.stage{
  position:sticky;top:0;height:100vh;overflow:hidden;
  background:#080B10;
}
/* NOTE: do not put transform:translateZ(0) on this element. It was tried, to
   make the sticky repositioning cheaper, and it caused a severe stutter. This
   box holds a <video> that is being seeked while the page scrolls; forcing it
   onto its own composited layer takes the video off the hardware overlay path
   and makes every frame paint the footage into the layer texture instead. The
   caption bands want translateZ, because they are text. This does not. */
.stage .poster{
  position:absolute;inset:0;background-size:cover;background-position:52% 50%;
  /* NO will-change, and NO transform. There is no camera here any more.
     This element is position:sticky, so it stays on screen through the whole of
     page two's arrival. While it was held at scale 1.12 the compositor had to
     keep an enlarged texture of a full-screen photograph and resample it, and
     that -- not who drove the scale -- is what made page two feel rough.
     Driving the same move from the compositor with a scroll timeline did not
     help, and neither did baking it at rest; the customer compared all of them.
     Do not put a transform back on this element. */
}
/* There is no <video> any more, and no camera either. One still photograph,
   painted once. Nothing here may set .poster to opacity 0: it is the picture,
   not a placeholder. The Back-button zoom these rules used to cause
   cannot happen now, because there is only ever one frame and it is the right
   one. */

/* global base scrim: no frame is ever raw behind the page */
.scrim{
  position:absolute;inset:0;pointer-events:none;
  /* Its own layer. This is a full-screen gradient that travels with the stage
     through every transition, and unpromoted it is re-rastered the whole way --
     which is what page two's flicker turned out to be, and it moved here the
     moment page two was fixed. Promoted individually and NOT by promoting
     .stage: the stage holds the video, and promoting a video's parent takes it
     off the hardware overlay path. That was measured, and it stuttered badly. */
  will-change:transform;
  background:radial-gradient(ellipse 120% 90% at 62% 45%,rgba(8,10,16,0) 34%,rgba(8,10,16,.66) 100%);
}

/* loading ring */
.ring{
  position:absolute;right:calc(var(--gut) + 4px);bottom:calc(var(--gut) + 4px);
  width:44px;height:44px;color:var(--accent);opacity:.85;transform:rotate(-90deg);
}
.ring circle{transition:stroke-dashoffset .18s linear}
/* --- the scroll hint: half hidden by design, and a real button --- */
.scrollhint{
  position:absolute;left:50%;bottom:clamp(18px,3.2vh,38px);z-index:4;
  /* Its opacity crosses zero every time the visitor leaves and returns to the
     hero, and a transparent element is not rastered -- so without a permanent
     layer this is repainted on every crossing too. Small, but it is the same
     fault as the bands above and costs nothing to close. */
  will-change:transform;
  transform:translateX(-50%);
  display:flex;flex-direction:column;align-items:center;gap:13px;
  background:none;border:0;padding:18px 30px;cursor:pointer;
  color:var(--text-primary);font-family:var(--mono);
  /* --hint carries the scroll-driven fade. keeping it a multiplier rather than an
     inline opacity means the hover state still wins, which an inline style would not. */
  opacity:calc(.42 * var(--hint,1));
  transition:opacity .55s var(--ease),transform .45s var(--ease);
}
/* A soft dark pool under the cue. This is what makes it easy to notice WITHOUT
   making it less transparent: the arrow keeps its .42, but it now always has
   something to be seen against, instead of dissolving wherever the photograph
   happens to be bright. Contrast is what the eye catches; alpha is not. */
.scrollhint::before{
  content:"";position:absolute;left:50%;top:50%;z-index:-1;
  width:280px;height:210px;transform:translate(-50%,-50%);
  border-radius:50%;pointer-events:none;
  background:radial-gradient(ellipse at center,
    rgba(5,7,12,.68) 0%, rgba(5,7,12,.40) 44%, rgba(5,7,12,0) 74%);
}
.scrollhint:hover,.scrollhint:focus-visible{opacity:calc(.95 * var(--hint,1))}
.scrollhint b{
  font-weight:500;font-size:16px;letter-spacing:.46em;
  /* Wide tracking adds a space after the LAST letter too, which pushes the word
     off centre under the arrow. Pulling one tracking unit back off the right
     edge re-centres it optically. Small, but it is the difference between set
     and typed. */
  margin-right:-.46em;
  text-shadow:var(--tshadow);
}
/* On a phone the word is dropped and the arrow carries the cue on its own:
   there is no hover there to explain it, the gesture is already the obvious one,
   and the label was competing with the headline for a narrow screen's attention. */
/* Roughly four times the old 24x14. The stroke is set in viewBox units, so it
   scales with the box -- 1.5 there lands the drawn line at about 5.5px, heavy
   enough to hold its own at this size without turning into a slab. */
.scrollhint svg{width:88px;height:50px;display:block;overflow:visible;
  filter:drop-shadow(0 2px 7px rgba(5,7,12,.92))}
.scrollhint svg path{
  fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;
}
.scrollhint .chev{animation:hintbob 2.6s var(--ease) -.5s infinite}
/* An animation nobody can see still costs a frame every frame. The cue is
   faded out for the whole of the rest of the page; the fade driver sets this
   when it reaches zero. */
.scrollhint.spent .chev{animation:none}
/* Travel scaled with the arrow, and a deeper pulse. Movement is the other half
   of being noticed at low opacity -- the eye is drawn to what moves. */
@keyframes hintbob{
  0%,100%{transform:translateY(0);opacity:.42}
  46%{transform:translateY(15px);opacity:1}
}
/* Placed after the desktop rule on purpose. Both selectors are .scrollhint .chev,
   so specificity is a tie and source order decides -- sitting above it, this had
   no effect at all and the phone kept the desktop bob. */
@media (max-width:720px){
  .scrollhint b{display:none}
  /* Bigger and a little more present than on a desktop, and this matters most
     when the phone has "reduce motion" switched on: that setting turns the bob
     off entirely -- correctly, it is an accessibility preference and not ours to
     override -- so on those devices size and contrast are the whole cue. */
  .scrollhint{gap:0;padding:16px 30px;opacity:calc(.52 * var(--hint,1))}
  .scrollhint svg{width:104px;height:60px}
  .scrollhint::before{width:320px;height:230px}
  .scrollhint .chev{animation:hintbobphone 2.1s var(--ease) -.4s infinite}
}
@keyframes hintbobphone{
  0%,100%{transform:translateY(0);opacity:.34}
  44%{transform:translateY(22px);opacity:1}
}
.statichero .scrollhint{position:absolute}

/* ===== 首屏向下滚动提示（2026-09-10 上线） =====
   上面那套 .scrollhint 是旧的，没有任何页面在用，标记早就撤了。这是新的一套。
   客户在 _hint-*.html 的开关上选的：32px、透明 42%、不要竖线、要上下浮动。 */
.dhint{
  position:absolute;left:50%;bottom:clamp(16px,3vh,34px);z-index:4;
  transform:translateX(-50%);
  display:flex;flex-direction:column;align-items:center;
  background:none;border:0;padding:14px 24px;cursor:pointer;
  color:var(--text-primary);
  /* 两个乘数：.42 是设计定的那个半透明值，--hint 是滚动带出来的淡出。
     写成乘法而不是直接设 opacity，hover 才压得住滚动那一路。 */
  opacity:calc(.42 * var(--hint,1));
  transition:opacity .45s var(--ease);
}
.dhint:hover,.dhint:focus-visible{opacity:calc(.92 * var(--hint,1))}
/* 照片有亮有暗，箭头压在亮处会没。一层很浅的投影够了，
   不用像旧那套一样在底下铺一整片光晕。 */
.dhint svg{width:32px;height:auto;display:block;overflow:visible;
  filter:drop-shadow(0 1px 4px rgba(5,7,12,.8))}
.dhint svg path{fill:none;stroke:currentColor;stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round}
@keyframes dhbob{0%,100%{transform:translateY(0)}50%{transform:translateY(5px)}}
.dhint .chev{animation:dhbob 2.4s var(--ease) infinite}
@media (prefers-reduced-motion:reduce){.dhint .chev{animation:none}}
@media (max-width:760px){
  .dhint{bottom:clamp(12px,2.2vh,26px);padding:12px 20px}
}
/* 首屏的字排到哪儿是固定的，屏幕矮下来就是首屏变矮，两者会撞。
   量出来：1440x900 箭头上面还有 50px，1440x700 只剩 30 出头。
   下面两级由脚本按实测余量挂上去，不是猜断点。 */
.dhint.tight{padding-top:4px;padding-bottom:6px}
.dhint.gone{display:none}


.cue{
  position:absolute;right:var(--gut);bottom:var(--gut);
  display:flex;flex-direction:column;align-items:center;gap:7px;
  font-family:var(--mono);font-size:10px;letter-spacing:.22em;color:var(--text-dim);
}
.cue i{display:block;width:1px;height:34px;background:linear-gradient(var(--text-dim),transparent);
  animation:cuefall 2.6s var(--ease) infinite}
@keyframes cuefall{0%{transform:scaleY(0);transform-origin:top}55%{transform:scaleY(1);transform-origin:top}
  56%{transform-origin:bottom}100%{transform:scaleY(0);transform-origin:bottom}}

/* --- caption bands: text lives in the LEFT third --- */
.band{
  position:absolute;left:var(--gut);top:0;height:100%;
  width:min(46ch,40vw);
  display:flex;flex-direction:column;justify-content:center;
  opacity:0;pointer-events:none;text-shadow:var(--tshadow);
  /* One permanent compositor layer per band, and this is what stops the text
     flickering. On Windows the browser draws text with ClearType subpixel
     antialiasing, but it switches that OFF for any element whose opacity is not
     exactly 1, and back ON when it returns to 1. Scrolling walks the band's
     opacity across that boundary again and again, and every crossing changes
     the weight of every glyph. Pinning the band to its own layer for its whole
     life settles the antialiasing mode once, so there is nothing left to
     switch. Measured before the fix: the two bands took 7 and 9 distinct
     opacity values through three wheel notches, every one of them a crossing.
     will-change AND translateZ. The comment here used to argue for translateZ
     alone, on the grounds that will-change is a hint the browser may drop --
     but Chrome collapses an identity 3D transform to a plain 2D matrix and
     then stops treating it as a promotion request at all. Measured:
     getComputedStyle returns matrix(1,0,0,1,0,0), not matrix3d. A request that
     never takes effect is worse than one that might occasionally be dropped,
     and without the layer this band is re-rastered from nothing every time it
     comes back -- a large block of text under a three-layer shadow, on every
     single crossing of the seam. */
  will-change:transform;
  transform:translateZ(0);
  backface-visibility:hidden;
}
.band > *{position:relative;z-index:1}
.band .btn{text-shadow:none}
.band.on{pointer-events:auto}
/* single-sided scrim anchored on the text column, dying out by 76% */
/* A left-anchored linear scrim, not a radial one. The text is a left-aligned column,
   and a radial gradient centred on it has already fallen to about 0.17 alpha by the
   time it reaches the right end of a headline, which measured 221 out of 255 under
   the worst frame. This holds above 0.6 across the whole text column and then fades. */
/* A left-anchored linear scrim, not a radial one. The text is a left-aligned column,
   and a radial gradient centred on it had already fallen to about 0.17 alpha by the
   right end of a headline, which measured 221 of 255 under the worst frame.
   Kept exactly horizontal: an angled gradient projects differently at the top and
   bottom edges of a tall box and leaves a visible seam. The element runs well past
   the text so the fade to zero happens off to the side of anything being read. */
/* The left-hand wash, now a stage element rather than a caption's ::before, so
   its density never moves. Same geometry the ::before had: it started 40% of a
   band's width to the left of the band and ran 240% of that width across, and
   the stops below are those stops re-expressed against this element's own box.
   Constant is the whole point -- the version that faded with the caption made
   the left of the picture pump brighter and darker on every handover. */
.sidewash{
  position:absolute;left:0;top:0;bottom:0;
  will-change:transform;   /* same reason as .scrim above */
  width:calc(var(--gut) + min(46ch,40vw) * 2.05);
  pointer-events:none;
  background:linear-gradient(90deg,
    rgba(5,7,12,.94) 0%,
    rgba(5,7,12,.90) 27%,
    rgba(5,7,12,.84) 51%,
    rgba(5,7,12,.62) 68%,
    rgba(5,7,12,.24) 85%,
    rgba(5,7,12,0) 98%);
}

.band h1,.band h2{
  font-family:var(--headline);font-weight:700;
  font-size:clamp(30px,4.3vw,58px);line-height:1.05;letter-spacing:-.02em;
  margin:0;font-variation-settings:'wdth' 110;
  text-wrap:balance;   /* keeps a lone orphan word off its own line */
}
.band .lede{
  font-size:clamp(15px,1.5vw,19px);color:var(--text-primary);opacity:.9;
  margin:18px 0 0;max-width:34ch;
}
.band .kick{
  font-family:var(--body);font-size:var(--label);font-weight:500;
  letter-spacing:.26em;color:var(--accent);margin:0 0 20px;
}

/* per-character / per-word split spans */
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.w{display:inline-block;white-space:pre}
/* No will-change here. It used to sit on .c, which meant 79 separate compositor
   layers for the two headlines -- one per character -- and only the .e-grid
   effect ever animates a character on its own. Neither band in use is e-grid,
   so all 79 were paid for and none were needed; the band above now carries the
   single layer that actually helps. */
.c{display:inline-block}

/* (a) approach-from-depth: words grow into place, echoing the forward push */
.e-approach .w{
  --kc:clamp(0,(var(--k,0) - var(--th,0))*2.6,1);
  opacity:var(--kc);
  /* TRANSLATE, NOT SCALE -- and this is a raster decision, not a taste one.
     --k is rewritten every frame of the 900ms opening ramp, so a scale here
     re-renders the glyphs of six words at a new size on every one of those
     frames, on the main thread, while the page is still loading. A translation
     reuses the texture already rastered and only moves it. .e-grid .c has
     always translated and has never been reported as stuttering. */
  transform:scale(calc(0.84 + 0.16 * var(--kc)));
  /* A permanent layer each. Measured: six words animate here and none of them
     was promoted, so six layers were being built and torn down, staggered,
     every time the caption came in -- which is the churn behind every stutter
     found on this site so far. Layout during the entrance is already zero and
     style is a few milliseconds, so the main thread was never the problem.
     Six small layers cost almost nothing and stop the antialiasing switching
     as each word crosses into full opacity. */
  will-change:transform;
}
/* (b) grid snap: characters slide in reading order, echoing steady travel */
.e-grid .c{
  --kc:clamp(0,(var(--k,0) - var(--th,0))*2.6,1);
  opacity:var(--kc);
  transform:translateX(calc((1 - var(--kc)) * var(--jx,0px)));
  will-change:transform;   /* same reason as .e-approach .w above */
}
/* (c) blur-to-sharp: two stacked copies, opacity only, static blur */
.e-blur{position:relative}
.e-blur .soft{
  position:absolute;inset:0;filter:blur(11px);
  opacity:calc(1 - clamp(0,var(--k,0)*1.35,1));
}
.e-blur .sharp{opacity:clamp(0,(var(--k,0) - .12)*1.5,1)}
/* (d) staged settle: headline words rise, then lede, then buttons.
   The three thresholds used to be .42 (last word), .58 and .74, each with a
   slow rate, so nothing finished before k=0.75 and the buttons not until 0.97.
   On a 270px hero that put the two call-to-action buttons at zero opacity for
   the first 200px -- three quarters of the whole scroll -- and fully lit only
   for the last 50. They are the call to action; they cannot spend the hero
   switched off. Rebalanced to finish at roughly k 0.31 / 0.41 / 0.52, which
   keeps the same three-step order but leaves the whole last wheel notch with
   everything present and clickable. */
/* NOTHING INSIDE THIS BAND ANIMATES. The band itself still fades as one
   element -- that is what keeps the two captions from overlapping -- but its
   eight words, its lede and its row are simply present.

   The customer judged three versions on their own machine and picked this one.
   The mechanism: an element whose opacity is strictly between 0 and 1 must be
   composited into its own texture and blended. Ten of them, over a playing
   video and two full-screen gradients, is ten extra blend passes per frame for
   the length of the transition -- and because the value comes from a custom
   property that JavaScript rewrites every scroll frame, the compositor can
   never own the animation: every frame costs style recalc, paint properties, a
   commit, and a fresh texture. One element fading is one blend. Ten is ten.

   DO NOT PUT THE PER-WORD ENTRANCE BACK, and do not trust this machine's
   numbers about it: the headless browser used for measurement reports its
   renderer as "Microsoft Basic Render Driver", the Windows software fallback.
   There is no GPU in it. On a CPU rasteriser these blend passes are a memcpy
   and disappear into the noise, which is why rAF timings, presented-frame
   timings, and even deleting the captions outright all came back identical. */
.e-settle .w{
  --kc:clamp(0,(var(--k,0) - var(--th,0))*6,1);
  opacity:var(--kc);
  transform:translateY(calc((1 - var(--kc)) * 22px));
}
.e-settle .lede{opacity:clamp(0,(var(--k,0) - .17)*5,1);
  transform:translateY(calc((1 - clamp(0,(var(--k,0) - .17)*5,1)) * 14px))}
.e-settle .row{opacity:clamp(0,(var(--k,0) - .26)*4.8,1);
  transform:translateY(calc((1 - clamp(0,(var(--k,0) - .26)*4.8,1)) * 14px))}

/* ONE control, not two chips.
   The pairing being avoided here is structural: a bright filled rectangle with a
   dark rectangle beside it. Recolouring keeps that construction, so the two
   actions become two segments of a single outlined object instead -- one border,
   one radius, divided by a hairline. Nothing is filled; the border does the
   work, and the accent survives as the primary segment's text. */
.row{
  display:inline-flex;flex-wrap:nowrap;gap:0;margin-top:32px;max-width:100%;
  border:1px solid var(--line);border-radius:3px;overflow:hidden;width:fit-content;
}
.row .btn{
  background:none;border:0;border-radius:0;
  /* Equal halves, not two labels each sized to its own words. `flex:0 1 auto`
     let every segment take exactly the width of its own text, so the pair read
     as two mismatched boxes: 169px against 136px in Chinese, 258 against 209 in
     English. `flex:1 1 0` divides the object evenly instead. The row is
     width:fit-content, so the padding below still decides how wide the whole
     control is -- it just stops deciding where the divider falls. Total width is
     unchanged; only the split moves to the middle. */
  padding:13px 26px;flex:1 1 0;min-width:0;
  transition:background .35s var(--ease),color .35s var(--ease);
}
/* the divider belongs to the first segment, so there is no gap to see */
.row .btn + .btn{box-shadow:inset 1px 0 0 var(--line)}
.row .btn-primary{color:var(--accent);text-shadow:none}
.row .btn-primary:hover{background:rgba(255,198,41,.10);transform:none}
.row .btn-ghost{color:var(--text-secondary)}
.row .btn-ghost:hover{background:rgba(255,255,255,.045)}
/* narrow screens keep the single object and stack the segments inside it */
@media (max-width:560px){
  .row{flex-direction:column;width:100%}
  .row .btn{padding:13px 22px}
  .row .btn + .btn{box-shadow:inset 0 1px 0 var(--line)}
}

/* static hero for phones and reduced motion */
/* 首屏只有一层。这个静态首屏原来只在手机和「减少动效」时出现，桌面走的是
   130vh 的推镜首屏（.hero）。客户要求首屏只有一页、去掉下滚停顿，所以推镜那
   一层无条件隐藏，这一层成为唯一的首屏。 */
.statichero{display:block;position:relative;z-index:1}
.hero{display:none !important}
/* On a phone this is the whole hero and 92vh is deliberate -- it lets the next
   section show a sliver and say there is more. On a DESKTOP it is only ever
   seen by someone with reduced motion turned on, and there 92vh of a 900px
   window leaves 72px of page two on screen from the moment the page loads --
   the same split, arrived at by standing still.

   Written as "full height, unless the pointer is coarse" rather than "full
   height when the pointer is fine", because a device can report neither: a
   headless browser answers pointer:none and so would have taken the phone
   branch. Only the phone case is asserted, and the phone reliably reports it. */
.statichero .inner{
  position:relative;min-height:100vh;display:flex;flex-direction:column;justify-content:center;
  padding:120px var(--gut) 72px;
  overflow:hidden;
}
@media (pointer:coarse) and (max-width:820px){ .statichero .inner{min-height:92vh} }
/* The camera move, phone edition. The desktop hero scrubs a 6.4MB video frame by
   frame, which is the wrong tool on a phone: it is real data on a metered
   connection and iOS restricts seeking hard enough that it stutters. This is the
   same idea done the way a phone can afford it -- the poster frame the static
   hero already downloaded, pushed in slowly on scroll. One element, one
   transform, no extra bytes at all.
   The scale runs 1 -> 1.14 and drifts a little up and right, the same direction
   the desktop move travels, so the two heroes feel like the same shot. */
.statichero .shot{
  position:absolute;inset:0;z-index:0;pointer-events:none;
  background-size:cover;background-position:58% 50%;
  transform:scale(calc(1 + .14 * var(--push,0)))
            translate3d(calc(var(--push,0) * -1.6%), calc(var(--push,0) * -1.1%), 0);
  transform-origin:58% 42%;
  will-change:transform;
}
.statichero .inner::after{
  content:"";position:absolute;inset:0;z-index:1;
  /* 这一层原来是按手机定的：左边起手 .90，因为手机上文字横跨整个屏宽，需要那么重。
     现在这个首屏也是桌面的首屏，文字只占左边一栏，.90 会把整张照片闷掉。
     整体减轻一档，右侧几乎放开。 */
  background:linear-gradient(100deg,rgba(8,10,16,.72) 0%,rgba(8,10,16,.50) 42%,rgba(8,10,16,.14) 100%);
}
/* a ch-based cap can exceed a narrow viewport, so clamp it to the container too */
/* 34ch 只有 298px，那是按手机窄屏定的。这个首屏现在也是桌面的首屏，
   298px 会把标题挤成三行。 */
.statichero .sh{position:relative;z-index:2;text-shadow:var(--tshadow);max-width:min(720px,100%)}

/* 首屏照片的收尾。原来首屏是 130vh 的 sticky 舞台，下边缘由接缝逻辑接管，
   看不见；机制拆掉之后照片是硬收的，跟下面那张照片之间会留下一道台阶。
   收到 rgb(9,12,17) —— 不是页面底色 #0B0F14。这两个黑差一点点，而页面二那层
   遮罩的顶端正是 rgba(9,12,17,.973)。两个纯色相接，差多少就是多明显的一条线，
   所以要对齐的是遮罩的顶端色，不是页面底色。
   ::after 用在这里是安全的：.statichero 自己的 ::before/::after 都还空着
   （压暗层挂在它里面的 .inner 上，不是它自己）。 */
.statichero::after{content:"";position:absolute;left:0;right:0;bottom:0;
  height:clamp(80px,10vh,150px);pointer-events:none;z-index:3;
  background:linear-gradient(0deg,rgb(9,12,17),rgba(9,12,17,0))}

.statichero h1{
  font-family:var(--headline);font-weight:700;font-size:clamp(34px,9vw,56px);
  line-height:1.03;letter-spacing:-.02em;margin:0;font-variation-settings:'wdth' 112;
}

/* ============================================================
   SECTIONS
   ============================================================ */
.sec{position:relative;z-index:1;padding:clamp(76px,10vw,148px) 0}
/* 这一层原来是给「面板」段落上的一层淡底：透明起手，到 14% 才到满值
   rgba(20,27,36,.5)，86% 之后再淡出。14% 在 900px 的段落上是 126px，也就是上下
   各有一条 126px 的渐变带。

   实测（review 里那次逐行取样）：段首亮度 14.0，15% 处升到 20.0，中段 19–20，
   段尾又落回 14.0 —— 中间比两端亮 6.3，看到的就是这个。

   接缝机制只管首屏到页面二那一段，最后这一页是自由滚动，视线会停在过渡带上，
   于是那两条带子藏不住了。分段本来就有琥珀分割线在标示，不靠这层底纹。
   白色版另有自己的 .sec.panel 规则（背景纯白），不受这条影响。 */
.sec.panel{background:none}

/* Every small label on the page reads from one token, so they scale together
   and can never drift apart again. 11px was genuinely small at this width; this
   lands near 15px on a desktop.
   The rule before the label is the craft: a bare tracked-out word is the default
   look of a hundred sites, an anchored one reads as set rather than typed. */
.kick{
  font-family:var(--body);font-size:var(--label);font-weight:500;
  letter-spacing:.26em;color:var(--accent);margin:0 0 20px;
  display:flex;align-items:center;gap:13px;width:fit-content;
}
.kick::before{content:"";width:34px;height:1px;background:currentColor;opacity:.9;flex:0 0 auto}

/* The factory's designation for the machine. It takes the kicker's slot at the
   top of a product page, and the kicker's treatment, because that is what it
   is -- the line that names the thing before the headline describes it. */
.model{
  font-family:var(--mono);font-size:var(--label);font-weight:500;
  letter-spacing:.22em;color:var(--accent);margin:0 0 16px;
  display:flex;align-items:center;gap:13px;width:fit-content;
}
.model::before{content:"";width:34px;height:1px;background:currentColor;opacity:.9;flex:0 0 auto}
/* on a card there is no room for the rule, and it has to sit quietly above the
   machine name rather than competing with it */
.card .model{font-size:calc(var(--label) * .78);letter-spacing:.16em;
  color:var(--text-dim);margin:0 0 7px}
.card .model::before{display:none}
a.card:hover .model{color:var(--accent)}
h2.big{
  font-family:var(--headline);font-weight:700;font-size:clamp(28px,4.1vw,54px);
  line-height:1.08;letter-spacing:-.02em;margin:0 0 22px;font-variation-settings:'wdth' 110;
}
h3{font-family:var(--display);font-weight:700;font-size:clamp(18px,1.8vw,23px);
  line-height:1.2;letter-spacing:-.01em;margin:0 0 10px;font-variation-settings:'wdth' 106}
p{margin:0 0 16px;color:var(--text-secondary)}
p.lead{font-size:clamp(16px,1.35vw,19px);color:var(--text-primary);opacity:.88;max-width:62ch}

/* the rule at the foot of every page. It used to carry the brand mark at its
   centre -- four curves opening out of the line and four closing back into it --
   and it is now one unbroken stroke, which is what the customer asked for. The
   44px height is kept: the line sits at its middle, so removing the mark moves
   nothing else on the page. */
.divider{display:block;width:100%;height:44px;overflow:visible;color:var(--accent-muted)}

/* A paged section is exactly one screen tall and carries its rule at the foot.
   The rule is absolutely positioned so it costs the page no height: in the flow
   it would put 44px between every pair of pages and they would stop being a
   screen apart.

   padding-block is tied to viewport height as well as width. The inherited
   clamp(76px,10vw,148px) spends 296px of a 900px screen, and the service page's
   content is 629px on its own -- 925px total, taller than the screen, so
   min-height would have had no effect there and that page alone would have been
   out of step with the rest. */
.sec.paged{
  position:relative;
  min-height:100vh;display:flex;align-items:center;
  padding-block:clamp(60px,10vh,148px);
}
.sec.paged > .wrap{width:100%}
.sec.paged > .divider{position:absolute;left:0;bottom:0;width:100%}
/* Drawn, not self-drawing. The draw-on was a stroke-dashoffset transition, and
   an SVG stroke animation is painted on the main thread every frame -- one was
   affordable, one per page is not, and the customer asked for the effect to go
   anyway. */
.divider path{stroke:currentColor;stroke-width:1.1;fill:none;stroke-linecap:round}

/* entrance choreography */
.rise{opacity:0;transform:translateY(26px);
  transition:opacity .9s var(--ease-out),transform .9s var(--ease-out)}
.in .rise,.rise.in{opacity:1;transform:none}
.stag>*:nth-child(1){transition-delay:.00s}
.stag>*:nth-child(2){transition-delay:.09s}
.stag>*:nth-child(3){transition-delay:.18s}
.stag>*:nth-child(4){transition-delay:.27s}
.stag>*:nth-child(5){transition-delay:.36s}
.stag>*:nth-child(6){transition-delay:.45s}
/* retire the stagger once the entrance is done, at matching specificity */
.stag.done>*:nth-child(1),.stag.done>*:nth-child(2),.stag.done>*:nth-child(3),
.stag.done>*:nth-child(4),.stag.done>*:nth-child(5),.stag.done>*:nth-child(6){transition-delay:0s}

/* --- full-bleed photo band, the first thing the hero hands over to --- */
.bleed{position:relative;z-index:1;height:clamp(340px,64vh,620px);overflow:hidden;background:#0E141B}
/* --- page two: the specification, set on the shop floor it describes --- */
/* The photograph fills the screen and the copy sits on top of it, so the scroll
   cue lands on a page that is taller than the viewport and nothing is cut. The
   veil is what makes this work: the photo has white machine panels in it, and
   the smallest text on the page -- the caption under each ordinal -- has to be
   read over them. Two stacked gradients rather than one flat scrim, so the
   picture keeps some depth instead of going uniformly grey. */
.sec.photo{
  /* height, centring and padding now come from .sec.paged, which every page
     from here down shares */
  /* 底色跟 body 取同一个值。原来是 #0E141B，比 body 的 #0B0F14 亮一档；这一段是
     整页唯一有不透明底色的区块，所以它和下面没有背景的 #converge 之间，在分割线
     处就多出一个色阶。 */
  overflow:hidden;background:#0B0F14;isolation:isolate;

  /* One compositor layer for the whole page, rastered once and afterwards only
     moved. Without it a 2200px photograph under three full-screen gradients is
     re-rastered tile by tile as it scrolls in, and tiles arriving late is what
     reads as flicker.

     The SECTION and not its children: promoting the photograph and the gradients
     alone would leave the copy painting above two composited layers, so the
     compositor would promote the copy too, by overlap, into a layer with
     nothing opaque behind it -- and that is where Windows subpixel
     antialiasing is lost. This element carries background:#0E141B, so the
     layer is opaque and the text keeps its subpixel rendering.

     will-change rather than translateZ(0): Chrome collapses an identity 3D
     transform to a plain 2D matrix and then stops treating it as a promotion
     request. Measured -- getComputedStyle returned matrix(1,0,0,1,0,0). */
  will-change:transform;
}
.sec.photo>.wrap{position:relative;z-index:2}
/* above the photograph and its veil, and above the rule at the foot */
.sec.photo>.divider{z-index:3}
.photobg{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  object-position:50% 52%;z-index:0;
  /* Opaque from the first frame, and no transition. Not for taste -- for RASTER
     TIMING. A fully transparent element is not rastered at all, so opacity:0
     deferred this photograph, and the two full-screen gradients painted over
     it, to the exact instant page two came into view. Measured on the
     customer's own machine: five frames of 58 to 108ms inside one 500ms window
     at that instant, with ZERO long tasks -- no script involved at all, which
     is what points at raster rather than anything on the main thread. Opaque,
     the browser rasters it whenever it likes during the twenty-odd seconds
     before anybody scrolls down to it.

     I measured this earlier and reported that removing the fade did not help.
     That was a local server in a headless browser, where raster costs almost
     nothing, and it hid the entire effect. Do not put the fade back.

     No scale either: a scaling layer is re-rastered the whole way, the same
     cost that stopped the ambient wash drifting. */
  opacity:1;
}
.sec.photo::after{
  content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:
    /* One gradient, not two. The top cap used to be a separate layer in pixels
       so it would not scale with the section -- but this section is always
       exactly 100vh, so a percentage of it is already a fixed fraction of the
       viewport and the cap folds in as ordinary stops. Every tile of this layer
       had to evaluate all three gradients; now it evaluates two, and the first
       raster of page two is the one moment that cost is visible.

       The first four stops ARE the cap: two layers at a and b compose to
       1-(1-a)(1-b), so 104px of cap over a 900px section lands at 0, 6.2, 8.7
       and 11.6 per cent. Its job is unchanged -- it darkens the strip of page
       two that sweeps across the screen during the transition, which was
       peaking at luma 197 against a hero bottom of about 105. */
    linear-gradient(180deg,
      rgba(9,12,17,.973) 0%,  rgba(9,12,17,.956) 6.2%,
      rgba(9,12,17,.587) 8.7%, rgba(9,12,17,.152) 11.6%,
      rgba(9,12,17,.30) 17%,  rgba(9,12,17,.46) 23%,
      rgba(9,12,17,.53) 30%,  rgba(9,12,17,.60) 38%,
      rgba(9,12,17,.70) 46%,  rgba(9,12,17,.80) 54%,
      rgba(9,12,17,.88) 62%,  rgba(9,12,17,.93) 70%,
      rgba(9,12,17,.96) 78%,  rgba(9,12,17,.98) 88%,
      rgba(9,12,17,.99) 100%),
    /* the two side edges are where the white machine panels are, and where the
       outer columns of type land; a light vignette, since the climb above now
       does most of the work */
    linear-gradient(90deg,rgba(9,12,17,.20) 0%,rgba(9,12,17,0) 32%,
                          rgba(9,12,17,0) 68%,rgba(9,12,17,.15) 100%);
}
/* the kicker and the headline sit high, where the veil is deliberately thin, so
   they carry their own contrast the way the hero captions over the video do */
.sec.photo .kick,.sec.photo h2.big{text-shadow:var(--tshadow)}
/* the step captions are the smallest type on the page and they sit over the
   busiest part of the frame; they get the primary text colour here rather than
   the dimmed secondary one they use on a flat panel */
.sec.photo .backw p{color:var(--text-primary);opacity:.92}
/* the kicker and the headline sit high, where the veil is deliberately thin, so
   they carry their own contrast the way the hero captions over the video do */
.sec.photo .kick,.sec.photo h2.big{text-shadow:var(--tshadow)}
/* the step captions are the smallest type on the page and they sit over the
   busiest part of the frame; they get the primary text colour here rather than
   the dimmed secondary one they use on a flat panel */
.sec.photo .backw p{color:var(--text-primary);opacity:.92}
/* the hairlines and the aside were drawn for a flat panel; over a photograph
   they need a little more weight or they disappear into it */
.sec.photo .backw li{border-top-color:rgba(255,255,255,.24)}

/* --- page two does not make an entrance ---
   It is the only section that is always reached through an animated transition,
   and a 1.7-second entrance on top of an 800ms transition is two animations
   stacked. Measured: 17 of them running at once on a first arrival, each one a
   compositor layer built and torn down. Everything here is simply present.
   Every other page is scrolled to normally and keeps its entrance. */
.sec.photo .rise{opacity:1;transform:none;transition:none}
.sec.photo .stag>*{transition-delay:0s !important}
/* The rules above the four steps are drawn, not drawn ON. Hovering a step
   brings its own rule up to full strength -- the customer's idea, except that
   they are present from the start: four missing rules above four numbered
   steps reads as unfinished, and a touch screen has no hover to give. */
.sec.photo .backw li::before{
  transform:scaleX(1);
  background:rgba(255,198,41,.42);
  transition:background-color .38s var(--ease);
}
.sec.photo .backw li:hover::before{background:var(--accent)}
.sec.photo .note{
  background:linear-gradient(90deg,rgba(255,198,41,.16),rgba(255,198,41,.06) 42%,rgba(9,12,17,.42));
}
.bleed img{width:100%;height:100%;object-fit:cover;object-position:50% 58%;display:block;
  transform:scale(1.06);transition:transform 1.8s var(--ease-out),opacity 1.2s var(--ease-out);opacity:0}
.bleed.in img{transform:scale(1);opacity:1}
.bleed::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,rgba(11,15,20,.92) 0%,rgba(11,15,20,.22) 26%,rgba(11,15,20,.22) 74%,rgba(11,15,20,.92) 100%);
}
.bleed figcaption{
  position:absolute;left:var(--gut);bottom:clamp(22px,4vw,44px);z-index:2;
  font-family:var(--mono);font-weight:500;font-size:var(--label);letter-spacing:.22em;
  color:var(--text-secondary);text-shadow:var(--tshadow);max-width:40ch;
}


/* --- the number --- */
/* --- specifying backwards, four steps --- */
.backw{list-style:none;margin:44px 0 0;padding:0;display:grid;
  grid-template-columns:repeat(4,1fr);gap:clamp(14px,1.8vw,26px);counter-reset:b}
.backw li{position:relative;padding-top:26px;border-top:1px solid var(--line)}
.backw li::before{
  content:"";position:absolute;left:0;top:-1px;width:100%;height:1px;background:var(--accent);
  transform:scaleX(0);transform-origin:left;
  /* transform, not width. Animating width runs layout every frame on the main
     thread -- measured at 348 layouts during one arrival at page two, in the
     same frames the transition needs. A scaleX draws identically and costs
     the compositor alone. */
  transition:transform 1.1s var(--ease-out);
}
.in .backw li::before{transform:scaleX(1)}
.in .backw li:nth-child(2)::before{transition-delay:.14s}
.in .backw li:nth-child(3)::before{transition-delay:.28s}
.in .backw li:nth-child(4)::before{transition-delay:.42s}
.backw .n{
  font-family:var(--mono);font-weight:500;font-size:var(--ordinal);
  letter-spacing:.14em;color:var(--accent);font-variant-numeric:tabular-nums;
  display:flex;align-items:center;gap:11px;width:fit-content;margin-bottom:12px;
}
.backw .n::after{content:"";width:28px;height:1px;background:currentColor;opacity:.55}
.backw h3{margin-bottom:8px}
.backw p{font-size:14.5px;margin:0}

/* An aside to the reader rather than another paragraph of body copy: it answers
   the question the four steps above raise ("do I have to buy all of it?"), so it
   is set apart -- an accent rule down its left edge, a panel behind it, and the
   room to be read on its own. */
.note{
  margin:30px 0 0;padding:20px 26px 21px;
  /* lifted off the section behind it: a warmer plate, a brighter edge and a
     wider accent bar, so it reads as an aside to the reader rather than another
     paragraph that happens to sit in a box */
  background:linear-gradient(90deg,rgba(255,198,41,.10),rgba(255,198,41,.035) 42%,rgba(255,198,41,0));
  border-left:3px solid var(--accent);
  border-top:1px solid rgba(255,198,41,.16);
  border-bottom:1px solid rgba(255,198,41,.16);
  border-radius:0 3px 3px 0;
  font-size:16.5px;line-height:1.6;color:var(--text-primary);max-width:66ch;
}


/* --- the hold moment --- */
.hold{display:grid;grid-template-columns:1fr 1fr;gap:clamp(28px,5vw,72px);align-items:center}
.holdstage{position:relative;aspect-ratio:1/1;max-width:440px;width:100%;margin-inline:auto}
.holdstage svg{width:100%;height:100%;display:block;overflow:visible}
.holdstage .stream{fill:none;stroke:var(--glow);stroke-width:1.6;stroke-linecap:round;opacity:.5}
/* Everything after the die. The nine streams above keep their own rule and
   their own colour; these are the bubble, the collapsing frame, the lay-flat
   film, the sealing bars and the bag they make. */
.holdstage .tube{fill:none;stroke:var(--glow);stroke-width:1.6}
.holdstage .web{fill:rgba(191,227,242,.07);stroke:var(--glow);stroke-width:1.4}
.holdstage .fold{fill:none;stroke:var(--glow);stroke-width:1;opacity:.55;stroke-dasharray:3 3}
/* The collapsing boards: the original size and the dashed line, which read
   better than the heavy version. What makes them noticed is that there are two
   a side, that they travel inward, and that they stay for the whole stage --
   not that they are large. */
.holdstage .board{fill:none;stroke:var(--glow);stroke-width:1;opacity:.8;stroke-dasharray:3 3}
.holdstage .sealbar{fill:none;stroke:var(--heat);stroke-width:3.2;stroke-linecap:round}
.holdstage .seal{fill:none;stroke:var(--accent);stroke-width:2.6;stroke-linecap:round}
.holdstage .knife{fill:none;stroke:var(--text-secondary);stroke-width:1.2;stroke-dasharray:2 4}
.holdstage .trim{fill:rgba(191,227,242,.05);stroke:var(--glow);stroke-width:.9;opacity:.5}
.holdstage .bag{fill:rgba(191,227,242,.09);stroke:var(--glow);stroke-width:1.6}
.holdstage .mouth{fill:none;stroke:var(--glow);stroke-width:1.4;opacity:.75}
.holdbtn{
  margin-top:26px;position:relative;overflow:hidden;
  background:var(--panel-2);border:1px solid var(--line);color:var(--text-primary);
  padding:15px 30px;border-radius:2px;font:700 13.5px/1 var(--body);letter-spacing:.03em;
  cursor:pointer;user-select:none;-webkit-user-select:none;touch-action:none;
  /* user-select stops the text being highlighted, but on iOS a long press also
     raises the copy/lookup callout, and only this turns that off. The whole
     interaction here IS a long press, so the callout was firing every time. */
  -webkit-touch-callout:none;
  transition:border-color .4s var(--ease),color .4s var(--ease);
}
.holdbtn::before{
  content:"";position:absolute;inset:0;background:var(--accent-muted);
  /* Scaled from the left rather than an inset that moves. --p is rewritten on
     every frame for the whole three and a half seconds the button is held, and
     an inset is a layout property -- driving it that way would run layout on
     every frame of the animation. This draws the same bar and never leaves the
     compositor. It was also simply not connected: --p was written and nothing
     read it, so the button gave no sign of progress at all. */
  transform:scaleX(var(--p,0));transform-origin:left;
  will-change:transform;
}
.holdbtn.armed{border-color:var(--accent);color:var(--accent)}
.card:hover{border-color:var(--accent-muted);transform:translateY(-4px)}
/* the four machine cards are links into the product area */
a.card{text-decoration:none;color:inherit}
a.card h3{transition:color .35s var(--ease)}
a.card:hover h3{color:var(--accent)}
a.card .ph{background:#fff}
.card .more{
  display:inline-flex;align-items:center;gap:7px;margin-top:16px;
  font-family:var(--mono);font-weight:500;font-size:var(--label);letter-spacing:.18em;color:var(--accent);
}
.card .more::after{content:"";width:14px;height:1px;background:var(--accent);
  transition:width .4s var(--ease)}
a.card:hover .more::after{width:26px}
/* No will-change here any more. It was added when the cards faded up from
   opacity 0, to stop each photograph being rastered at the instant it came into
   view. The cards no longer fade -- see products.css -- so nothing is ever
   transparent, and twenty-five permanent compositor layers on one page would be
   cost with nothing bought. */
.card .ph{aspect-ratio:16/10;overflow:hidden;background:#0E141B}
.card .ph img{width:100%;height:100%;object-fit:cover;display:block;
  transition:transform 1.1s var(--ease)}
.card:hover .ph img{transform:scale(1.045)}
.card .bd{padding:22px 22px 26px}
.card ul{margin:12px 0 0;padding:0;list-style:none;display:grid;gap:7px}
.card li{font-family:var(--mono);font-size:11.5px;letter-spacing:.02em;color:var(--text-dim);
  display:flex;gap:9px}
.card li b{color:var(--text-secondary);font-weight:500;min-width:8.5ch}

/* --- spec table --- */
.spec{width:100%;border-collapse:collapse;margin-top:26px;font-family:var(--body);font-size:13px}
.spec caption{text-align:left;color:var(--text-dim);font-size:11px;letter-spacing:.2em;
  padding-bottom:14px;font-family:var(--mono)}
.spec th,.spec td{padding:13px 14px;border-bottom:1px solid var(--line);text-align:left}
.spec thead th{color:var(--text-dim);font-weight:500;font-size:11px;letter-spacing:.16em;
  border-bottom-color:var(--accent-muted)}
.spec tbody th{color:var(--text-secondary);font-weight:400;font-family:var(--mono)}
.spec td{color:var(--text-primary);font-variant-numeric:tabular-nums;font-family:var(--mono)}
.spec tr.hi td{color:var(--accent);font-weight:500}
.specwrap{overflow-x:auto;-webkit-overflow-scrolling:touch}

/* --- four answers --- */
.ans{display:grid;grid-template-columns:repeat(2,1fr);gap:clamp(16px,2vw,26px);margin-top:36px}
.ans article{background:var(--panel);border:1px solid var(--line);border-left:2px solid var(--accent-muted);
  padding:26px 26px 28px;border-radius:0 3px 3px 0;
  transition:border-left-color .45s var(--ease)}
.ans article:hover{border-left-color:var(--accent)}
.ans .n{
  font-family:var(--mono);font-weight:500;font-size:var(--ordinal);
  letter-spacing:.14em;color:var(--accent);font-variant-numeric:tabular-nums;
  display:flex;align-items:center;gap:11px;width:fit-content;margin-bottom:13px;
}
.ans .n::after{content:"";width:28px;height:1px;background:currentColor;opacity:.55}

/* --- stats --- */
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(14px,2vw,30px);margin-top:40px}
.stat{border-top:1px solid var(--line);padding-top:20px}
.stat b{display:block;font-family:var(--display);font-weight:700;font-size:clamp(30px,4vw,50px);
  line-height:1;letter-spacing:-.02em;color:var(--text-primary);font-variant-numeric:tabular-nums}
.stat span{display:block;margin-top:10px;font-family:var(--mono);font-weight:500;
  font-size:var(--label);letter-spacing:.14em;color:var(--text-dim)}

.shots{display:grid;grid-template-columns:1.4fr 1fr;gap:clamp(12px,1.4vw,18px);margin-top:44px}
.shots figure{margin:0;overflow:hidden;border-radius:3px;background:#0E141B}
.shots img{width:100%;height:100%;object-fit:cover;display:block;aspect-ratio:16/10}
.shots .tall img{aspect-ratio:4/5}

/* --- faq --- */
.faq{margin-top:30px;border-top:1px solid var(--line)}
.faq details{border-bottom:1px solid var(--line)}
.faq summary{
  list-style:none;cursor:pointer;padding:20px 44px 20px 0;position:relative;
  font-family:var(--display);font-weight:600;font-size:clamp(15px,1.5vw,18px);
  color:var(--text-primary);transition:color .35s var(--ease);
}
.faq summary::-webkit-details-marker{display:none}
.faq summary:hover{color:var(--accent)}
/* 箭头用 currentColor，跟着 summary 的文字走：静止时是正文白，悬停时 summary
   自己把 color 换成琥珀，箭头就跟着变。这样两者永远同色，不用再写一组颜色去
   手动同步——以后改文字色，箭头自动跟上。 */
.faq summary::after{
  content:"";position:absolute;right:8px;top:50%;width:11px;height:11px;
  border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
  transform:translateY(-70%) rotate(45deg);
  transition:transform .4s var(--ease),border-color .35s var(--ease);
}
.faq details[open] summary::after{transform:translateY(-30%) rotate(225deg)}
.faq .a{padding:0 0 22px;max-width:74ch}

/* --- form --- */
.formwrap{display:grid;grid-template-columns:.9fr 1.1fr;gap:clamp(30px,5vw,72px);align-items:start}
.field{display:grid;gap:9px;margin-bottom:18px}
.field label{font-family:var(--mono);font-weight:500;font-size:var(--label);
  letter-spacing:.18em;color:var(--text-secondary)}
.field input,.field select,.field textarea{
  background:var(--panel);border:1px solid var(--line);border-radius:2px;
  color:var(--text-primary);padding:13px 14px;font:inherit;font-size:15px;width:100%;
  /* The system stack, not the site's. The Chinese webfont is a subset built from
     the characters this site sets, and nothing a visitor types is in it -- their
     own name would render in a fallback while everything around it did not.
     Their machine's own interface font is the right thing here regardless. */
  font-family:system-ui,-apple-system,'Segoe UI','Microsoft YaHei','PingFang SC',sans-serif;
  transition:border-color .35s var(--ease),background .35s var(--ease);
}
.field input:focus,.field select:focus,.field textarea:focus{
  outline:none;border-color:var(--accent);background:var(--panel-2)}
.field textarea{min-height:112px;resize:vertical}
.pair{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.sent{
  display:none;border:1px solid var(--accent-muted);background:rgba(255,198,41,.06);
  padding:26px;border-radius:3px;
}
form.done .sent{display:block}
form.done .fields{display:none}

/* --- footer --- */
/* 下边距归零：最底一栏自带背景，footer 再留一段下边距会在灰条下面露出底色。 */
footer{position:relative;z-index:1;
  padding:clamp(28px,3.4vw,40px) 0 0;margin-top:clamp(60px,8vw,110px)}
/* 灰底上沿那条线，跟版权行上面那条完全同一套：同样用背景画（border-width 会被
   Chrome 向上取整到整数 CSS 像素，做不出亚像素）、同样 var(--dp) 一个设备像素、
   同样 var(--line) 取色。两条线是同一个写法，就不会一条粗一条细。 */
footer::before{content:"";position:absolute;top:0;left:0;right:0;
  height:var(--dp, 1px);background:var(--line)}
/* 通栏的底栏。背景铺满整个宽度，里面的 .wrap 把文字拉回栏线上。

   分隔线照 Vitsœ 做：实测它是 0.8px solid rgb(194,194,194)、通栏。那个 0.8px
   不是随手取的——它测到的 devicePixelRatio 是 1.25，0.8 × 1.25 正好是 1 个设备
   像素。所以这里用 var(--dp)（一个设备像素合多少 CSS 像素，由 mega.js 量出来
   写在 <html> 上），任何缩放比例下都是干净的一个设备像素，而不是把 0.8px 抄过来。

   颜色用 var(--line) 就够：白色版里它本来就是 #C2C2C2，跟 Vitsœ 量到的一致；
   深色页面上它自动是深色版的线色。 */
.legalbar{margin-top:24px;position:relative}
/* 这条线用背景画，不用 border。实测 Chrome 会把 border-width 向上取整到整数
   CSS 像素：0.8px、0.5px、var(--dp) 三种写法算出来全是 1px，在 125% 缩放下就是
   1.25 个设备像素，跨两行各画一半。同一个尺寸交给背景就是真的亚像素——量到
   0.796875px（Chrome 的 1/64 长度网格），×1.25 ≈ 1 个设备像素。
   mega.js 里那些 hairline 一直是这么画的，这里照它。 */
.legalbar::before{content:"";position:absolute;top:0;left:0;right:0;
  height:var(--dp, 1px);background:var(--line)}
/* 深色页（也就是首页）上，页脚的两条线用品牌琥珀而不是暗灰 —— 客户要求。
   白色页面不在此列：那两条上一轮刚照 Vitsœ 定成 #C2C2C2，所以这里用
   :not([data-skin="light"]) 把它们排除掉。 */
html:not([data-skin="light"]) footer::before,
html:not([data-skin="light"]) .legalbar::before{background:var(--accent-muted)}
.foot{display:grid;grid-template-columns:1.2fr 1fr 1fr;gap:clamp(24px,4vw,60px)}
.foot h4{font-family:var(--body);font-size:var(--label);letter-spacing:.22em;color:var(--text-dim);
  margin:0 0 12px;font-weight:500}
.foot a{color:var(--text-secondary);text-decoration:none;transition:color .3s var(--ease)}
.foot a:hover{color:var(--accent)}
/* 系列链接的悬停黄线，跟导航下拉面板里的是同一个做法：一条实心的品牌色下划线，
   贴在文字下面。高度和位置都取整到整数设备像素，否则 125% 缩放下这条 1px 的线
   会跨两个设备像素行各画一半，看起来比该有的细。
   用负的 bottom 定位而不是给链接加下内边距——加内边距会把四行撑开。 */
.foot li a{position:relative;display:inline-block}
.foot li a::before{content:"";position:absolute;left:0;right:0;
  bottom:round(-4px, var(--dp, 1px));height:round(1px, var(--dp, 1px));
  background:var(--accent);opacity:0;transition:opacity .3s var(--ease)}
.foot li a:hover::before{opacity:1}
.foot ul{margin:0;padding:0;list-style:none;display:grid;gap:8px;font-size:16.5px}
/* Shares the lockup's column: same left edge, same measure. It is NOT tracked
   to finish on the lockup's right edge -- that only ever worked for one exact
   Chinese sentence, and the English line is long enough to wrap anyway. Left
   edge and column width are what make it read as part of the same block; a
   forced right edge would have been a trick with a single sentence's lifespan. */
/* The strapline is exactly as wide as the lockup above it, at every width --
   which matters because the lockup SHRINKS once the footer column drops below
   about 210px, while a fixed 208px strapline did not. Measured at 700px: the
   lockup had come down to 150px and the text was still running 45px past its
   right edge, which is the imbalance that kept showing up.
   width:0 with min-width:100% is what makes it work: the paragraph contributes
   nothing to the column's intrinsic width, so the column is sized by the lockup
   alone, and then the paragraph fills whatever that turns out to be. */
/* 第一栏，照 Vitsœ：一个大标题加一句话。实测它那块是 32px/40px、weight 400。 */
.foot .keepcol{max-width:34ch}
.foot .keep{font-family:var(--headline);font-weight:700;font-size:clamp(24px,2.4vw,32px);
  line-height:1.25;letter-spacing:-.01em;margin:0 0 10px;color:var(--text-primary)}
/* 这一句要落成两行，而且断在逗号上。原来只有 max-width 管着，断点落在
   「才会促成彼此 / 的想法」中间——把一个词劈开了。text-wrap:balance 让浏览器
   自己把两行配平，实测断在逗号后。max-width 收到 15em 是给它一个可断的范围，
   太宽就成一行了。 */
.foot .keepline{margin:0;font-size:16px;line-height:1.6;color:var(--text-secondary);
  max-width:15em;text-wrap:balance}
/* 按 Vitsœ 的结构，那一句下面接一个动作按钮。它那颗是描边加箭头，这里换成本站
   自己的主按钮，样式不另起一套。 */
.foot .keepbtn{margin:16px 0 0}
/* 外部账号那一排。圆圈描边用 1px 而不是 var(--dp)：实测 Chrome 会把 border-width
   向上取整到整数 CSS 像素，小数写了也是 1px，所以这里不假装。圆圈短，取整一格
   看不出来；页脚那条通栏长线才必须用背景画。
   地址还没填，所以是 span 不是 a：不可点、压暗、鼠标移上去显示「待提供」，
   跟联系栏里「电话 / WhatsApp 待提供」是同一种处理。 */
.foot .socrow{display:flex;flex-wrap:wrap;gap:10px;margin:16px 0 0}
.foot .soc{width:34px;height:34px;border-radius:50%;display:grid;place-items:center;
  border:1px solid var(--line);color:var(--text-secondary);
  transition:border-color .3s var(--ease),color .3s var(--ease)}
.foot .soc svg{width:17px;height:17px;display:block}
.foot a.soc{text-decoration:none}
.foot a.soc:hover{border-color:var(--accent);color:var(--accent)}
.foot .soc-todo{opacity:.42;cursor:default}
/* The lockup slides left so the LEAF, not its box, sits on the page gutter --
   then the strapline below needs no indent at all and the two share a real edge.

   The offset is 6/24 of the mark's width, and that 6 is the part worth writing
   down. The path's control points are at x=4, so 4/24 looks like the answer and
   is what I used first; it left the text six pixels out and no amount of box
   arithmetic explained why. A cubic Bezier never reaches its control points.
   Evaluating the curve at t=0.5 puts the leftmost ink at x=6, not x=4. Measured
   afterwards by finding the leftmost lit pixel in a screenshot: 0px apart. */
/* Two parts to this offset.

   6/24 of the mark's width puts the leaf's leftmost INK on the page gutter. The
   6 is worth writing down: the path's control points are at x=4, so 4/24 looks
   like the answer and is what I used first -- it left the text six pixels out
   and no amount of box arithmetic explained why. A cubic Bezier never reaches
   its control points; evaluated at t=0.5 the leftmost ink is at x=6.

   --mark-optical is the second part, and it is a judgement, not a measurement.
   The leaf touches its leftmost point at one tangent halfway up and curves away
   above and below it, so a shape that is mathematically flush still reads as
   inset next to a solid row of glyphs. Overshooting to the left is what makes
   it look aligned. This is the number to turn if it wants to go further either
   way -- everything else here is fixed by geometry. */
.foot .brand{margin-left:calc(var(--mark) * -6 / 24 - var(--mark-optical,11.5px))}
/* The sentence sits on the column edge, level with the LOCKUP AS A WHOLE -- not
   with the type inside it. The mark carries its negative margin precisely so its
   leftmost ink lands on that same line, so this is where the two agree. An
   earlier attempt aligned it to REIZE TECH, 50px further in; that lines the
   sentence up with the type and out of line with the logo.

   width:0;min-width:100% pins the sentence to the width the lockup establishes,
   which is what makes the English wrap there rather than setting the column
   width itself. The English is long and one line of it is far too wide. */
.foot .strap{margin:0;font-size:12.7px;line-height:1.65;color:var(--text-secondary);
  width:0;min-width:100%}
/* Chinese only, and it fits: thirteen full-width characters at 12.7px is about
   165px inside the 191px the lockup establishes. */
.foot .strap [lang="zh"]{white-space:nowrap}

/* 上面那条实线去掉了。字号从 13px 提到 16px——量的是 Vitsœ 的版权行，
   它跟自己的页脚链接同样大（16px/24px），不是缩成一行小字。 */
/* 这一栏两次降高：68 → 58（-15%）→ 52（再 -10%）。文字那 24px 是字号 16px ×
   行高 1.5 决定的、不该动，所以全部从内边距里出：22 → 17 → 14。
   再往下压就只能动字号了，而字号是上一轮照 Vitsœ 定的 16px。 */
/* 版权行里的 REIZE MACHINERY，把那个词间距收紧。
   这个值原来是照导航标志定的：标志当时是一行「REIZE TECH」，Archivo 20.7px，
   中间那个空格 4.69px = 0.226em；版权行是 IBM Plex Mono 16px，等宽字体的空格
   本身就宽，量到 9.61px = 0.601em——所以看着松。0.226 - 0.601 = -0.375em。

   2026-09-10 字标换成方案六之后，REIZE 和 MACHINERY 分成了两行，标志里
   已经没有空格可对了。值保留，因为收紧的真正理由跟导航无关：等宽字体的
   空格就是比例上偏宽，收到 0.226em 才是正常的词间距。

   只加在这两个词上，不加在整行：整行加会把「© 2026」和「锐泽机械」前后的
   空格也一起收掉。 */
.legal .wm{word-spacing:-0.375em}
.legal{margin:0;padding-block:14px;
  font-family:var(--mono);font-size:16px;line-height:1.5;color:var(--text-dim);
  display:flex;flex-wrap:wrap;gap:8px 22px;justify-content:space-between}

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media (max-width:1000px){
  .hold,.formwrap{grid-template-columns:1fr}
  .ans,.shots{grid-template-columns:1fr}
  .stats{grid-template-columns:repeat(2,1fr)}
}
/* The nav has to hold the brand, five links, the language switch and a wide button.
   Measured: it overflows below about 1000px in English, so the links tighten first
   and then step aside, leaving the two controls a visitor actually needs. */
@media (max-width:1180px){
  .navlinks{gap:clamp(12px,1.5vw,20px)}
  .navlinks .btn{padding:4px 10px;min-height:0}
}
@media (max-width:1000px){
  .navlinks a:not(.btn){display:none}
}
@media (max-width:760px){
  .grid4{grid-template-columns:1fr}
  .pair{grid-template-columns:1fr}
  /* four columns of Chinese at phone width is five characters a line */
  .backw{grid-template-columns:repeat(2,1fr);gap:clamp(18px,5vw,26px) clamp(16px,4vw,26px)}
  /* the lockup follows the mark on its own now, so this is the only number */
  :root{--mark:69px}
  /* the column narrows here, so let the Chinese wrap again rather than run off
     the side of a phone */
  .foot .strap [lang="zh"]{white-space:normal}   /* 60 x 1.15 */
}
/* the nav's own button carries a short label for the narrowest phones, where the
   full one costs about 58px the nav does not have */
.lbl-short{display:none}
@media (max-width:430px){
  .navlinks .lbl-long{display:none}
  .navlinks .lbl-short{display:inline}
  .nav{padding-inline:16px;gap:10px}
  :root{--mark:62.1px}   /* 54 x 1.15 */
  .navlinks .btn{padding:3px 9px;font-size:13.5px;min-height:0}
  html[data-lang="zh"] .navlinks .btn{font-size:14px}
  .langsw{font-size:10.5px;gap:5px}
}
/* A short screen cannot hold the service page at full spacing: four cards, a
   headline and a lede come to 629px in Chinese and 714px in English, and
   768px of screen minus even tight padding does not cover it. Every page has
   to be the same height, so this is where the content gives instead. Nothing
   here applies above 820px. */
@media (max-height:820px){
  .sec.paged{padding-block:clamp(36px,5.5vh,148px)}
  .ans{gap:clamp(12px,1.4vw,26px);margin-top:24px}
  .ans article{padding:18px 20px 19px}
  .ans p{font-size:14px}
  .sec.paged p.lead{max-width:78ch}
  .backw{margin-top:30px}
}

/* short screens: the scrub stays, small overlays have no room */
@media (max-height:560px){.cue,.ring{display:none}}

/* 原来这里有「五道闸」：五个媒体查询决定桌面用推镜首屏、手机用静态首屏。
   现在首屏只有一层，静态首屏在所有断点下都显示，这组闸门没有意义了，删掉。 */

/* ============================================================
   REDUCED MOTION: honoured completely, and in both directions
   ============================================================ */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important;animation-iteration-count:1 !important;
    transition-duration:.001ms !important;transition-delay:0s !important;
  }
  /* the hint stops bobbing but stays a working button */
  .scrollhint .chev{animation:none;transform:none;opacity:1}
  .rise{opacity:1;transform:none}
  .backw li::before{transform:scaleX(1)}
}
body.paused *,body.paused *::before,body.paused *::after{animation-play-state:paused !important}

/* ============================================================
   手机上按电脑的比例收首屏
   ============================================================
   见 review/mobile-desktop-view.js。只在竖视口生效，桌面不受影响。

   这一段必须留在**最大**的那个 style 块里：synccss.js 只把最大的一块写进
   assets/site.css，另外 28 个页面读的是那份。放进小块的后果实测过一次 ——
   首页正常、其余 28 页的首屏在手机上照旧被撑到 100vh。

   --heroBottom 用 html 起头是为了压过后面 v3 那段里的 #hero{--heroBottom}，
   两者权重本来都是 (1,0,0)，靠位置分胜负，而这一段在前面。 */
@media (max-aspect-ratio:1/1){
  .sec.paged{min-height:66.6vw;padding-block:clamp(60px,6.25vw,148px)}
  html #hero{--heroBottom:clamp(44px,3.44vw,68px)}
}
