	<style id="csm-ui-v1-fonts">
/*
 * CSM UI v1: the fonts, declared in the page (a font declared inside a
 * shadow root is ignored). Printed inline once per page by
 * csm_ui_print_assets(), with the fonts folder pointed at the plugin. The
 * families have their own names, so a font the site declares can never
 * take their place:
 *   csm-ui-display  Saira Semi Condensed 600, 700, 800 (titles, ranks, big figures)
 *   csm-ui-label    Nunito Sans 400 to 800 (labels)
 *   csm-ui-data     Roboto 400 to 700 (data, tabular numbers)
 * The files are the latin and latin-ext subsets that Google Fonts serves
 * (woff2), under the SIL Open Font License 1.1 (assets/fonts/OFL-*.txt).
 */
@font-face{font-family:csm-ui-label;font-style:normal;font-weight:400 800;font-display:swap;src:url(https://canadiansupermoto.com/wp-content/plugins/csm-ui/assets/fonts/nunito-sans-latin-ext.woff2) format("woff2");unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:csm-ui-label;font-style:normal;font-weight:400 800;font-display:swap;src:url(https://canadiansupermoto.com/wp-content/plugins/csm-ui/assets/fonts/nunito-sans-latin.woff2) format("woff2");unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:csm-ui-data;font-style:normal;font-weight:400 700;font-display:swap;src:url(https://canadiansupermoto.com/wp-content/plugins/csm-ui/assets/fonts/roboto-latin-ext.woff2) format("woff2");unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:csm-ui-data;font-style:normal;font-weight:400 700;font-display:swap;src:url(https://canadiansupermoto.com/wp-content/plugins/csm-ui/assets/fonts/roboto-latin.woff2) format("woff2");unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:csm-ui-display;font-style:normal;font-weight:600;font-display:swap;src:url(https://canadiansupermoto.com/wp-content/plugins/csm-ui/assets/fonts/saira-semi-condensed-600-latin-ext.woff2) format("woff2");unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:csm-ui-display;font-style:normal;font-weight:600;font-display:swap;src:url(https://canadiansupermoto.com/wp-content/plugins/csm-ui/assets/fonts/saira-semi-condensed-600-latin.woff2) format("woff2");unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:csm-ui-display;font-style:normal;font-weight:700;font-display:swap;src:url(https://canadiansupermoto.com/wp-content/plugins/csm-ui/assets/fonts/saira-semi-condensed-700-latin-ext.woff2) format("woff2");unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:csm-ui-display;font-style:normal;font-weight:700;font-display:swap;src:url(https://canadiansupermoto.com/wp-content/plugins/csm-ui/assets/fonts/saira-semi-condensed-700-latin.woff2) format("woff2");unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:csm-ui-display;font-style:normal;font-weight:800;font-display:swap;src:url(https://canadiansupermoto.com/wp-content/plugins/csm-ui/assets/fonts/saira-semi-condensed-800-latin-ext.woff2) format("woff2");unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:csm-ui-display;font-style:normal;font-weight:800;font-display:swap;src:url(https://canadiansupermoto.com/wp-content/plugins/csm-ui/assets/fonts/saira-semi-condensed-800-latin.woff2) format("woff2");unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
	</style>
	<script type="text/css" id="csm-ui-v1" data-build-name="CSM UI" data-build="1.2.0" data-build-url="https://canadiansupermoto.com/wp-content/plugins/csm-ui/assets/build.json">
/*
 * CSM UI v1: the look shared by the CSM plugins. It applies inside each
 * plugin's shadow root, where the site's CSS can't reach: the plugin's
 * script copies it there (plugins/csm-ui/src/v1/shadow.ts), from the copy
 * that csm_ui_print_assets() prints once per page, and gives the plugin's
 * root element the class csm-ui. The fonts are declared in the page
 * (fonts.css). Tokens: --csm-*, from the approved Classements mockup (v10).
 * One light theme, like the site.
 *
 * Nothing comes from the page: the root starts from the initial values
 * (all:initial) and sets the tokens and the base text itself, so even a
 * site rule on the host element (the only one the site can style, and
 * which its content would inherit from) changes nothing inside.
 *
 * v1 only grows: a new token or rule is fine, but one that changes the
 * look of what a plugin already shows, or goes away, makes v2 (a new
 * assets/v2/ beside this one, which stays).
 */

:host{all:initial;display:block}
.csm-ui{
  all:initial;
  display:block;
  box-sizing:border-box;
  color-scheme:light;
  --csm-ink:#121417;
  --csm-text:#454a52;
  --csm-muted:#80868f;
  --csm-line:#e4e6ea;
  --csm-line-strong:#cfd3d9;
  --csm-panel:#f5f6f8;
  --csm-bg:#ffffff;
  --csm-gold:#B8952E; --csm-gold-bg:#fbf5e4;
  --csm-silver:#8C8C8C; --csm-silver-bg:#f2f2f2;
  --csm-bronze:#A5673A; --csm-bronze-bg:#f8ede5;
  --csm-dnf:#b23b2e;
  --csm-live:#1f8a4c;
  --csm-accent:#121417;
  --csm-f-display:csm-ui-display,'Arial Narrow',Arial,sans-serif;
  --csm-f-label:csm-ui-label,Helvetica,Arial,sans-serif;
  --csm-f-data:csm-ui-data,Helvetica,Arial,sans-serif;
  --csm-radius:10px;
  color:var(--csm-text);
  font-family:var(--csm-f-data);
  font-size:16px;
  -webkit-font-smoothing:antialiased;
}
*{box-sizing:border-box}
button{font:inherit;color:inherit}
:focus-visible{outline:2px solid var(--csm-accent);outline-offset:2px}
@media (prefers-reduced-motion:reduce){
	*{transition:none!important;scroll-behavior:auto!important}
	}

/*
 * The « back to top » button (shadow.ts, mountToTop), once per page, in a
 * plugin's shadow root beside its root: outside .csm-ui, so it starts from
 * the initial values too and uses the tokens' values. Above a plugin's
 * sticky bars, below a full-screen layer (z-index 50 and up).
 */
.csm-ui-top{
  all:initial;
  position:fixed;
  right:max(16px,env(safe-area-inset-right));
  bottom:max(16px,env(safe-area-inset-bottom));
  z-index:40;
  display:flex;
  align-items:center;
  justify-content:center;
  width:44px;
  height:44px;
  border-radius:50%;
  background:#121417;
  color:#fff;
  box-shadow:0 6px 18px rgba(18,20,23,.18);
  cursor:pointer;
  opacity:0;
  visibility:hidden;
  transform:translateY(8px);
  pointer-events:none;
  transition:opacity .2s,transform .2s,visibility .2s,background-color .2s;
}
.csm-ui-top.on{opacity:1;visibility:visible;transform:none;pointer-events:auto}
.csm-ui-top[hidden]{display:none}
.csm-ui-top:hover{background:#2a2e35}
.csm-ui-top:focus-visible{outline:2px solid #121417;outline-offset:2px}
.csm-ui-top svg{fill:currentColor}
@media (min-width:600px){
  .csm-ui-top{right:max(24px,env(safe-area-inset-right));bottom:max(24px,env(safe-area-inset-bottom))}
}
@media print{.csm-ui-top{display:none}}
	</script>
		<style>
	/*
	 * CSM Standings: the only rules for the site's own page, printed in it
	 * (everything else is in the plugin's shadow root: csm-standings.css). No
	 * square brackets here: WordPress would run a bracketed shortcode name.
	 *
	 * The site's Elementor "Single Page" template shows the page title (a The7
	 * Heading widget and the spacer after it) above the page's content. The
	 * widget has its own title, so the theme's is hidden on its pages; the
	 * template's colored band stays.
	 *
	 * The theme's back-to-top button shows on desktops only: on its pages the
	 * widget has its own, on every screen (CSM UI), so the theme's is hidden.
	 */
	.e-con:has(>.elementor-widget-theme-post-content :is(csm-classements,csm-verification))>.elementor-widget-the7-heading,
	.e-con:has(>.elementor-widget-theme-post-content :is(csm-classements,csm-verification))>.elementor-widget-the7-heading+.elementor-widget-spacer{display:none}
	body:has(:is(csm-classements,csm-verification)) a.scroll-top{display:none!important}
	</style>
	<script type="text/css" id="csm-standings-css" data-build-name="CSM Standings" data-build="5.20.0" data-build-url="https://canadiansupermoto.com/wp-content/plugins/csm-standings/assets/build.json">
	/*
	 * CSM Standings: the Classements page (the csm_classements shortcode) and
	 * the Vérification page (the csm_verification shortcode). No square brackets
	 * here: this stylesheet is printed in the page, where WordPress would run
	 * a bracketed shortcode name. It applies inside the plugin's shadow root,
	 * on top of the shared look (CSM UI v1: tokens --csm-*, fonts, base
	 * styles), so the site's CSS can't reach it. The rules for the site's own
	 * page are in csm-standings-site.css. The Classements rules reproduce the
	 * approved mockup v10 (design/classements-mockup/mockup-v10.html).
	 */

	/* The widget breaks out of the theme's container to a full-width white
	   band; its content is centered at 1120px (.wrap). */
	.csm-widget{width:100vw;position:relative;left:50%;right:50%;margin-left:-50vw;margin-right:-50vw;background:var(--csm-bg)}
	.csm-widget .wrap{max-width:1120px;margin:0 auto;padding:24px 16px 48px}
	@media (min-width:600px){
		.csm-widget .wrap{padding-inline:24px}
		}
	/* ---- page header ---- */
	.csm-widget .top{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-bottom:18px}
	.csm-widget .eyebrow{font:800 11px/1 var(--csm-f-label);letter-spacing:.14em;text-transform:uppercase;color:var(--csm-muted);margin:0 0 6px}
	.csm-widget .top h1{font:800 italic clamp(28px,5vw,40px)/1 var(--csm-f-display);color:var(--csm-ink);margin:0;letter-spacing:-.01em;text-transform:uppercase}
	/* ---- championship bar ---- */
	.csm-widget .bar{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:10px;margin:0 0 22px}
	.csm-widget .tile{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;min-height:108px;padding:10px 8px 12px;background:var(--csm-bg);border:1px solid var(--csm-line);border-radius:var(--csm-radius);cursor:pointer;transition:border-color .15s,box-shadow .15s,transform .15s}
	.csm-widget .tile:hover{border-color:var(--csm-line-strong);box-shadow:0 2px 10px rgba(18,20,23,.06)}
	.csm-widget .tile img{height:52px;width:auto;max-width:100%;object-fit:contain}
	.csm-widget .tile .code{font:800 11px/1 var(--csm-f-label);letter-spacing:.12em;color:var(--csm-muted)}
	.csm-widget .tile[aria-pressed="true"]{border-color:var(--c);box-shadow:0 0 0 1px var(--c),0 6px 18px rgba(18,20,23,.08)}
	.csm-widget .tile[aria-pressed="true"] .code{color:var(--c)}
	.csm-widget .tile[aria-pressed="true"]::after{content:"";position:absolute;left:18%;right:18%;bottom:-1px;height:3px;border-radius:3px 3px 0 0;background:var(--c)}
	.csm-widget .tile.overview{--c:var(--csm-ink)}
	.csm-widget .tile .spill{font-size:9px;padding:3px 7px}
	@media (max-width:899px){
		.csm-widget .bar{display:flex;overflow-x:auto;scroll-snap-type:x mandatory;padding:2px 2px 8px;margin-inline:-16px;padding-inline:16px;scroll-padding-inline:16px;scrollbar-width:none}
		.csm-widget .bar::-webkit-scrollbar{display:none}
		.csm-widget .tile{flex:0 0 128px;scroll-snap-align:start;min-height:98px}
		.csm-widget .tile img{height:42px}
		}
	/* ---- shared ---- */
	.csm-widget .section-h{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:30px 0 12px}
	.csm-widget .section-h h2{font:800 italic 20px/1.1 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink);margin:0;letter-spacing:.01em}
	.csm-widget .section-h .aside{font:600 12px var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .chip{display:inline-flex;align-items:center;gap:5px;font:800 10px/1 var(--csm-f-label);letter-spacing:.08em;padding:4px 7px;border-radius:999px;color:#fff;background:var(--c);white-space:nowrap}
	.csm-widget .pts{font:700 15px var(--csm-f-data);font-variant-numeric:tabular-nums;color:var(--csm-ink)}
	.csm-widget .pts small{font:600 11px var(--csm-f-label);color:var(--csm-muted);margin-left:2px}
	/* A standing's status: a pale pill (final cream ★, live pale green ●, to come outlined, blocked grey), then its detail */
	.csm-widget .status{display:inline-flex;flex-wrap:wrap;align-items:center;gap:4px 8px;font:700 12px var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .spill{display:inline-flex;align-items:center;gap:5px;font:800 10px/1 var(--csm-f-label);letter-spacing:.08em;text-transform:uppercase;padding:4px 8px;border-radius:999px;white-space:nowrap;border:1px solid transparent}
	.csm-widget .spill.final{background:var(--csm-gold-bg);color:#7a5f14;border-color:#ead9a6}
	.csm-widget .spill.final::before{content:"★"}
	.csm-widget .spill.live{background:#eef7f1;color:var(--csm-live);border-color:#cfe6d8}
	.csm-widget .spill.live::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor}
	.csm-widget .spill.upcoming{background:var(--csm-bg);color:var(--csm-text);border-color:var(--csm-line-strong)}
	.csm-widget .spill.blocked{background:var(--csm-panel);color:var(--csm-muted);border-color:var(--csm-line-strong)}
	.csm-widget .link{font:700 13px var(--csm-f-label);color:var(--csm-ink);text-decoration:none;border-bottom:1px solid var(--csm-line-strong)}
	.csm-widget .link:hover{border-color:var(--csm-ink)}
	.csm-widget .notice{padding:18px;border:1px dashed var(--csm-line-strong);border-radius:var(--csm-radius);font:600 14px var(--csm-f-label);color:var(--csm-muted);text-align:center;background:var(--csm-panel)}
	/* ---- dashboard ---- */
	.csm-widget .races{display:grid;grid-template-columns:1fr;gap:12px;margin-top:14px}
	@media (min-width:700px){
		.csm-widget .races{grid-template-columns:1.25fr 1fr 1fr}
		}
	.csm-widget .race{padding:16px 18px;border:1px solid var(--csm-line);border-radius:var(--csm-radius);display:flex;flex-direction:column;gap:8px}
	.csm-widget .race.next{background:var(--csm-ink);border-color:var(--csm-ink);color:#d9dce1}
	.csm-widget .race .rtop{display:flex;align-items:center;justify-content:space-between;gap:8px}
	.csm-widget .race.next .sched{color:#9ba1ab}
	.csm-widget .race.next .sched:hover{color:#fff}
	.csm-widget .race .k{font:800 11px var(--csm-f-label);letter-spacing:.12em;text-transform:uppercase;color:var(--csm-muted)}
	.csm-widget .race.next .k{color:#9ba1ab}
	.csm-widget .race .n{font:800 italic 26px/1 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink)}
	.csm-widget .race.next .n{color:#fff}
	.csm-widget .race .d{font:600 13px var(--csm-f-label)}
	.csm-widget .race .chips{display:flex;gap:6px;flex-wrap:wrap}
	.csm-widget .count{font:800 italic 40px/1 var(--csm-f-display);color:#fff}
	.csm-widget .count small{font:700 13px var(--csm-f-label);font-style:normal;color:#9ba1ab;margin-left:6px}
	.csm-widget .cal{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(196px,1fr);grid-template-rows:repeat(4,auto);gap:6px 0;overflow-x:auto;border:1px solid var(--csm-line);border-radius:var(--csm-radius);scrollbar-width:thin}
	/* the races share the rows (date, tag and schedule; name; venue; tags), so each row lines up across the strip; all columns have the same width */
	.csm-widget .cal-item{grid-row:1/-1;display:grid;grid-template-rows:subgrid;padding:12px 12px 14px;border-right:1px solid var(--csm-line);position:relative}
	.csm-widget .cal-item:last-child{border-right:0}
	.csm-widget .cal-item .when{display:flex;flex-wrap:wrap;align-items:center;align-content:flex-start;gap:4px 8px}
	.csm-widget .cal-item .sched{align-self:center;margin-left:auto}
	.csm-widget .cal-item .dt{white-space:nowrap;font:800 11px/22px var(--csm-f-label);letter-spacing:.06em;text-transform:uppercase;color:var(--csm-muted)}
	.csm-widget .cal-item .ev{font:700 13px var(--csm-f-data);color:var(--csm-ink);word-break:break-word}
	.csm-widget .cal-item .ve{font:600 11px var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .cal-item .chips{display:flex;gap:4px;flex-wrap:wrap;align-content:flex-start}
	.csm-widget .cal-item.past{background:var(--csm-panel)}
	.csm-widget .cal-item.past .ev{color:var(--csm-text)}
	.csm-widget .cal-item.live{box-shadow:inset 0 3px 0 var(--csm-live)}
	.csm-widget .cal-item.upcoming.first{box-shadow:inset 0 3px 0 var(--csm-ink)}
	.csm-widget .tag{font:800 9px var(--csm-f-label);letter-spacing:.1em;text-transform:uppercase}
	.csm-widget .tag.live{color:var(--csm-live)}
	.csm-widget .tag.next{color:var(--csm-ink)}
	.csm-widget .leaders{display:grid;grid-template-columns:1fr;gap:14px}
	@media (min-width:760px){
		.csm-widget .leaders{grid-template-columns:1fr 1fr}
		}
	.csm-widget .lpanel{border:1px solid var(--csm-line);border-radius:var(--csm-radius);overflow:hidden}
	.csm-widget .lpanel header{position:relative;display:flex;align-items:center;gap:12px;padding:12px 14px;border-bottom:1px solid var(--csm-line);border-top:3px solid var(--c)}
	/* the header opens the championship: a button over all of it; the stakes line sits above it */
	.csm-widget .lgo{position:absolute;inset:0;z-index:0;width:100%;padding:0;border:0;background:none;cursor:pointer}
	.csm-widget .lgo:focus-visible{outline:2px solid var(--c);outline-offset:-2px}
	.csm-widget .lpanel header:hover .t{text-decoration:underline;text-underline-offset:3px}
	.csm-widget .lgo-i{font-style:normal;color:var(--csm-muted)}
	.csm-widget .lpanel header .lsum{position:relative;z-index:1}
	.csm-widget .lpanel header img{height:34px;width:auto}
	.csm-widget .lpanel header .t{font:800 italic 16px/1.1 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink)}
	.csm-widget .lpanel header .s{font:600 11px var(--csm-f-label);color:var(--csm-muted);margin-top:2px}
	.csm-widget .lpanel ol{list-style:none;margin:0;padding:0}
	.csm-widget .lrow{display:grid;grid-template-columns:92px 1fr auto;align-items:center;gap:10px;padding:8px 14px;border-top:1px solid var(--csm-line);cursor:pointer;background:none;border-left:0;border-right:0;border-bottom:0;width:100%;text-align:left}
	.csm-widget .lrow:first-child{border-top:0}
	.csm-widget .lrow:hover{background:var(--csm-panel)}
	.csm-widget .lrow .cl{font:800 11px var(--csm-f-label);letter-spacing:.06em;color:var(--csm-muted);text-transform:uppercase}
	.csm-widget .lrow .nm{font:600 14px var(--csm-f-data);color:var(--csm-ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
	.csm-widget .lrow .gap{font:600 11px var(--csm-f-label);color:var(--csm-muted);margin-left:6px}
	.csm-widget .lrow.blocked .nm{color:var(--csm-muted);font-style:italic;font-weight:400}
	.csm-widget .lst{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:50%;font:800 10px/1 var(--csm-f-label);font-style:normal;background:var(--csm-gold-bg);color:#7a5f14;box-shadow:inset 0 0 0 1px var(--csm-gold)}
	/* The line under a championship that opens Courses on the race that decides the titles. */
	.csm-widget .lsum{display:inline-flex;align-items:center;gap:6px;margin-top:6px;padding:0;border:0;background:none;font:700 12px/1.3 var(--csm-f-label);color:var(--csm-ink);text-align:left;cursor:pointer}
	.csm-widget .lsum span{font-size:14px;color:var(--csm-muted)}
	.csm-widget .lsum:hover{text-decoration:underline}
	.csm-widget .lsum:focus-visible{outline:2px solid var(--csm-ink);outline-offset:2px;border-radius:4px}
	/* The legend of the ✓, under the panels. */
	.csm-widget .llegend{display:flex;flex-wrap:wrap;gap:6px 16px;margin:10px 0 0;font:600 11px var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .llegend span{display:inline-flex;align-items:center;gap:6px}
	/* ---- championship banner ---- */
	.csm-widget .banner{display:flex;align-items:center;gap:18px;padding:16px 18px;border:1px solid var(--csm-line);border-left:4px solid var(--c);border-radius:var(--csm-radius);flex-wrap:wrap}
	.csm-widget .banner img{height:64px;width:auto}
	.csm-widget .banner .t{font:800 italic clamp(20px,3.4vw,28px)/1.05 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink);margin:0 0 6px}
	.csm-widget .banner .meta{display:flex;flex-wrap:wrap;gap:6px 16px;align-items:center;font:600 12px var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .banner .grow{flex:1;min-width:220px}
	.csm-widget .fchip{border:1px solid var(--csm-line-strong);background:var(--csm-bg);border-radius:999px;padding:6px 12px;font:700 12px var(--csm-f-label);color:var(--csm-text);cursor:pointer}
	.csm-widget .fchip[aria-pressed="true"]{background:var(--csm-ink);border-color:var(--csm-ink);color:#fff}
	.csm-widget .fclear{border:0;background:none;font:700 12px var(--csm-f-label);color:var(--csm-muted);text-decoration:underline;cursor:pointer}
	.csm-widget .cards{display:grid;grid-template-columns:1fr;gap:14px}
	@media (min-width:600px){
		.csm-widget .cards{grid-template-columns:1fr 1fr}
		}
	@media (min-width:1000px){
		.csm-widget .cards{grid-template-columns:1fr 1fr 1fr}
		}
	/* category tabs */
	.csm-widget .tabs-wrap{position:relative;margin-bottom:14px}
	.csm-widget .tabs{display:flex;gap:4px;overflow-x:auto;scrollbar-width:none;border-bottom:1px solid var(--csm-line);scroll-behavior:smooth}
	.csm-widget .tabs::-webkit-scrollbar{display:none}
	.csm-widget .tabs button{flex:0 0 auto;border:0;background:none;padding:10px 12px;font:800 12px var(--csm-f-label);letter-spacing:.04em;color:var(--csm-muted);cursor:pointer;border-bottom:3px solid transparent;margin-bottom:-1px;white-space:nowrap}
	.csm-widget .tabs button[aria-selected="true"]{color:var(--csm-ink);border-bottom-color:var(--c)}
	.csm-widget .tabs button.blk{color:var(--csm-line-strong)}
	.csm-widget .detail-head{display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:12px}
	.csm-widget .detail-head h3{margin:0;font:800 italic 24px/1 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink)}
	/* table */
	.csm-widget .tscroll{overflow-x:auto;border:1px solid var(--csm-line);border-radius:var(--csm-radius);margin-top:16px}
	.csm-widget .section-h+.tscroll{margin-top:0}
	.csm-widget table{border-collapse:separate;border-spacing:0;width:100%;font-variant-numeric:tabular-nums}
	.csm-widget th,.csm-widget td{padding:9px 10px;border-bottom:1px solid var(--csm-line);text-align:center;white-space:nowrap;font-size:13px;background:var(--csm-bg)}
	.csm-widget tbody tr:last-child td{border-bottom:0}
	.csm-widget th{font:800 10px/1.25 var(--csm-f-label);letter-spacing:.06em;text-transform:uppercase;color:var(--csm-muted);cursor:pointer;user-select:none;vertical-align:bottom;background:var(--csm-panel)}
	.csm-widget th .dt{display:block;font-weight:600;letter-spacing:0;text-transform:none;color:var(--csm-muted);font-size:10px;margin-top:2px}
	.csm-widget th[aria-sort]{color:var(--csm-ink)}
	.csm-widget th[aria-sort="ascending"]::after{content:" ▲"}
	.csm-widget th[aria-sort="descending"]::after{content:" ▼"}
	.csm-widget td.nm{text-align:left;font:600 13px var(--csm-f-data);color:var(--csm-ink)}
	.csm-widget td.rk{font:800 italic 16px var(--csm-f-display);color:var(--csm-ink)}
	.csm-widget tr.r1 td.rk{color:var(--csm-gold)}
	.csm-widget tr.r2 td.rk{color:var(--csm-silver)}
	.csm-widget tr.r3 td.rk{color:var(--csm-bronze)}
	.csm-widget td.tot{font-weight:700;color:var(--csm-ink)}
	.csm-widget td.gross{color:var(--csm-muted)}
	.csm-widget td .sp,.csm-widget .tcell .sp{color:var(--csm-dnf);font-weight:600;font-size:12px}
	.csm-widget td .na,.csm-widget .tcell .na{color:var(--csm-line-strong)}
	.csm-widget .sticky{position:sticky;z-index:1}
	.csm-widget .s1{left:0;min-width:44px}
	.csm-widget .s2{left:44px;min-width:50px}
	.csm-widget .s3{left:94px;box-shadow:1px 0 0 var(--csm-line)}
	/* on a phone only the rank and the rider stay stuck, the name cut with "…", so the results show (user, 2026-09-27) */
	@media (max-width:599px){
		.csm-widget .s2{position:static}
		.csm-widget .s3{left:44px}
		.csm-widget td.s3 .who{display:block;max-width:92px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
		}
	@media (min-width:900px){
		.csm-widget .sticky{position:static}
		.csm-widget .s3{box-shadow:none}
		}
	.csm-widget .tnote{font:600 12px var(--csm-f-label);color:var(--csm-muted);margin:10px 2px 0}
	/* riders */
	.csm-widget .search{position:relative;margin-bottom:12px;max-width:520px}
	.csm-widget .search input{width:100%;font:500 15px var(--csm-f-data);padding:12px 40px 12px 14px;border:1px solid var(--csm-line-strong);border-radius:8px;color:var(--csm-ink);background:var(--csm-bg)}
	.csm-widget .search button{position:absolute;right:6px;top:50%;transform:translateY(-50%);border:0;background:none;font-size:20px;color:var(--csm-muted);cursor:pointer;padding:4px 8px}
	/* a rider search's ×: ours only (the same on phones), and only when there is text */
	.csm-widget .search input::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none;display:none}
	.csm-widget .search input:placeholder-shown+button{display:none}
	/* ---- Pilotes v2 ---- */
	.csm-widget .who{border:0;background:none;padding:0;font:inherit;color:inherit;cursor:pointer;text-align:inherit;text-decoration:underline;text-decoration-color:transparent;text-underline-offset:3px;transition:text-decoration-color .15s}
	.csm-widget .who:hover{text-decoration-color:currentColor}
	/* ---- rider page ---- */
	.csm-widget .back{border:0;background:none;font:700 13px var(--csm-f-label);color:var(--csm-ink);cursor:pointer;padding:6px 0;margin-bottom:10px}
	.csm-widget .phero{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:12px 24px;padding-bottom:18px;border-bottom:2px solid var(--csm-ink)}
	.csm-widget .phero h2{font:800 italic clamp(30px,6vw,52px)/.95 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink);margin:6px 0 8px;letter-spacing:-.01em;text-wrap:balance}
	.csm-widget .plates{display:flex;gap:6px;flex-wrap:wrap}
	.csm-widget .plate{font:800 italic 20px/1 var(--csm-f-display);color:var(--csm-ink);border:2px solid var(--csm-ink);border-radius:6px;padding:4px 10px 3px;background:var(--csm-bg)}
	.csm-widget .plate small{font:700 10px var(--csm-f-label);font-style:normal;color:var(--csm-muted);margin-right:4px;letter-spacing:.06em}
	.csm-widget .pstats{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1px;background:var(--csm-line);border:1px solid var(--csm-line);border-radius:var(--csm-radius);overflow:hidden;margin-top:18px}
	@media (min-width:640px){
		.csm-widget .pstats{grid-template-columns:repeat(4,minmax(0,1fr))}
		}
	.csm-widget .pstats div{background:var(--csm-bg);padding:12px 14px}
	.csm-widget .pstats b{display:block;font:800 italic 28px/1 var(--csm-f-display);color:var(--csm-ink)}
	.csm-widget .pstats span{font:700 11px var(--csm-f-label);letter-spacing:.08em;text-transform:uppercase;color:var(--csm-muted)}
	.csm-widget .ecards{display:grid;grid-template-columns:1fr;gap:12px}
	@media (min-width:600px){
		.csm-widget .ecards{grid-template-columns:1fr 1fr}
		}
	@media (min-width:1000px){
		.csm-widget .ecards{grid-template-columns:1fr 1fr 1fr}
		}
	.csm-widget .ecard{border:1px solid var(--csm-line);border-top:3px solid var(--c);border-radius:var(--csm-radius);padding:14px;display:grid;grid-template-columns:auto 1fr;gap:4px 14px;align-items:center}
	.csm-widget .ecard img{height:30px;width:auto}
	.csm-widget .ecard .lab{font:800 11px var(--csm-f-label);letter-spacing:.08em;text-transform:uppercase;color:var(--csm-muted)}
	.csm-widget .ecard .lab b{color:var(--csm-ink)}
	.csm-widget .ecard .big{grid-column:1/-1;display:flex;align-items:baseline;gap:10px;margin-top:6px}
	.csm-widget .ecard .big .pos{font:800 italic 44px/1 var(--csm-f-display);color:var(--csm-ink)}
	.csm-widget .ecard .big .pos.m1{color:var(--csm-gold)}
	.csm-widget .ecard .big .pos.m2{color:var(--csm-silver)}
	.csm-widget .ecard .big .pos.m3{color:var(--csm-bronze)}
	.csm-widget .ecard .big .of{font:600 13px var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .ecard .line{grid-column:1/-1;display:flex;justify-content:space-between;gap:8px;font:600 12px var(--csm-f-label);color:var(--csm-muted);flex-wrap:wrap}
	.csm-widget .ecard .line .pts{font-size:16px}
	.csm-widget .ecard .go{grid-column:1/-1;justify-self:start;border:0;background:none;padding:6px 0 0;font:700 12px var(--csm-f-label);color:var(--csm-ink);cursor:pointer}
	.csm-widget .sched{display:inline-flex;align-items:center;justify-content:center;padding:3px;border-radius:6px;color:var(--csm-text);text-decoration:none}
	.csm-widget .sched:hover{color:var(--csm-ink)}
	.csm-widget .sched:focus-visible{outline:2px solid currentColor;outline-offset:1px}
	.csm-widget .nextrow{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;margin-top:10px;padding-top:10px;border-top:1px solid var(--csm-line);font:600 12px var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .nextrow .k{font:800 10px var(--csm-f-label);letter-spacing:.12em;text-transform:uppercase}
	.csm-widget .nextrow b{font:800 italic 16px var(--csm-f-display);color:var(--csm-ink);text-transform:uppercase}
	.csm-widget .cgroups{display:flex;flex-direction:column;gap:14px}
	.csm-widget .cgroup{border:1px solid var(--csm-line);border-top:3px solid var(--c);border-radius:var(--csm-radius);padding:12px 14px 14px}
	.csm-widget .cg-h{display:flex;align-items:center;gap:12px;margin-bottom:12px}
	.csm-widget .cg-h img{height:38px;width:auto}
	.csm-widget .cg-h .t{font:800 italic 17px/1.1 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink)}
	.csm-widget .cg-h .s{font:600 11px var(--csm-f-label);color:var(--csm-muted);margin-top:2px}
	.csm-widget .cgroup .ecard{grid-template-columns:1fr;border-top:1px solid var(--csm-line);background:var(--csm-bg)}
	.csm-widget .cgroup .ecards{grid-template-columns:repeat(auto-fill,minmax(220px,1fr))}
	.csm-widget .cc{display:inline-flex;align-items:center;gap:4px;font:800 10px var(--csm-f-label);letter-spacing:.06em;color:var(--csm-ink);border:1px solid var(--csm-line-strong);border-left:3px solid var(--c);border-radius:5px;padding:2px 6px;background:var(--csm-bg)}
	.csm-widget .cc em{font-style:normal;font-weight:800;font-size:9px;color:var(--csm-muted);text-transform:uppercase}
	/* race by race on a phone: the race as a header, then one small card per class */
	/* season grid (racing-CV style) */
	.csm-widget .hero-r{display:flex;flex-direction:column;align-items:flex-end;gap:10px}
	@media (max-width:599px){
		.csm-widget .hero-r{align-items:flex-start}
		}
	.csm-widget .copy{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--csm-line-strong);background:var(--csm-bg);border-radius:999px;padding:7px 12px;font:700 12px var(--csm-f-label);color:var(--csm-ink);cursor:pointer;max-width:100%;overflow-wrap:anywhere}
	.csm-widget .copy:hover{border-color:var(--csm-ink)}
	.csm-widget .gbox{position:relative;border:1px solid var(--csm-line);border-radius:var(--csm-radius);padding:10px 10px 8px}
	.csm-widget .gscroll{overflow-x:auto;scrollbar-width:thin}
	.csm-widget table.grid{border-collapse:separate;border-spacing:0;width:100%}
	.csm-widget .grid th,.csm-widget .grid td{border:0;background:var(--csm-bg);padding:3px 2px;text-align:center;white-space:nowrap}
	.csm-widget .grid thead th{vertical-align:bottom;padding-bottom:6px;cursor:default;background:var(--csm-bg)}
	.csm-widget .grid .ge{display:block;font:800 10px var(--csm-f-label);letter-spacing:.04em;color:var(--csm-ink)}
	.csm-widget .grid .gd{display:block;font:600 9px var(--csm-f-label);color:var(--csm-muted);text-transform:none;letter-spacing:0}
	.csm-widget .grid .gcls{position:sticky;left:0;z-index:1;text-align:left;font:800 12px var(--csm-f-label);color:var(--csm-ink);padding-right:10px;box-shadow:1px 0 0 var(--csm-line)}
	.csm-widget .gcodes{display:flex;gap:3px;margin-top:3px}
	.csm-widget .gcodes i{width:12px;height:3px;border-radius:2px;background:var(--c)}
	.csm-widget .sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
	.csm-widget .grid td.off{background:repeating-linear-gradient(135deg,var(--csm-panel) 0 4px,var(--csm-bg) 4px 8px);border-radius:6px}
	.csm-widget .gc{position:relative;display:inline-flex;align-items:center;justify-content:center;min-width:32px;height:32px;padding:0 4px;border-radius:50%;border:1px solid var(--csm-line-strong);background:var(--csm-bg);font:800 italic 14px var(--csm-f-display);color:var(--csm-ink);cursor:pointer;font-variant-numeric:tabular-nums}
	.csm-widget .gc.p1{background:var(--csm-gold);border-color:var(--csm-gold);color:#fff}
	.csm-widget .gc.p2{background:var(--csm-silver);border-color:var(--csm-silver);color:#fff}
	.csm-widget .gc.p3{background:var(--csm-bronze);border-color:var(--csm-bronze);color:#fff}
	.csm-widget .gc.bad{border-radius:8px;border-color:transparent;color:var(--csm-dnf);font:800 10px var(--csm-f-label);font-style:normal}
	.csm-widget .gc.abs,.csm-widget .gc.out{border-color:transparent;color:var(--csm-muted)}
	.csm-widget .gc.nr{border-style:dashed;font-size:12px}
	.csm-widget .gc.fut{border-style:dotted;border-color:var(--csm-line-strong)}
	.csm-widget .gc.jk{box-shadow:0 0 0 2px var(--csm-bg),0 0 0 3px var(--csm-muted);text-decoration:line-through;text-decoration-thickness:1px}
	.csm-widget .gc.sel{outline:2px solid var(--csm-ink);outline-offset:2px}
	.csm-widget .gdetail{min-height:2.6em;margin:8px 2px 4px;padding:8px 10px;border-radius:8px;background:var(--csm-panel);font:600 12px/1.6 var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .gdetail b{color:var(--csm-ink)}
	.csm-widget .gdetail .gv b{font:800 italic 14px var(--csm-f-display);color:var(--csm-ink)}
	.csm-widget .gdetail .cc{margin:0 2px}
	.csm-widget .gdetail .sched{margin-left:6px;vertical-align:middle}
	/* the grid's legend: a button in the section header opens it (a help, below) */
	.csm-widget .section-r{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:6px 10px}
	.csm-widget .legend-btn{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--csm-line-strong);background:var(--csm-bg);border-radius:999px;padding:3px 10px 3px 3px;font:700 12px var(--csm-f-label);color:var(--csm-ink);cursor:pointer}
	.csm-widget .legend-btn:hover{border-color:var(--csm-ink)}
	.csm-widget .legend-btn .q{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;background:var(--csm-ink);color:#fff;font:800 11px var(--csm-f-label)}
	/* ---- the Joker (art. 19.4): only where it explains a figure, the rule itself linked to the rulebook (user, 2026-09-30) ---- */
	.csm-widget{--jk:#5b45b0}
	/* the table: a thin violet ring around the result the Joker drops (like the season grid), the value grey and struck */
	.csm-widget .jcell{display:inline-flex;align-items:center;justify-content:center;min-width:30px;height:24px;padding:0 6px;border-radius:999px;box-shadow:inset 0 0 0 1.5px var(--jk)}
	.csm-widget .jcell .jk{color:var(--csm-muted);text-decoration:line-through;text-decoration-color:rgba(128,134,143,.7)}
	.csm-widget .jcell .jk .sp{color:inherit}
	/* the link to the rulebook's article: under the table, and on the rider page */
	.csm-widget .jnote{margin-top:12px}
	.csm-widget .jlink{font:700 12px var(--csm-f-label);color:var(--jk)}
	.csm-widget a.jlink{text-decoration:underline;text-underline-offset:2px}
	/* the rider page's card: one line */
	.csm-widget .ejoker{grid-column:1/-1;margin:6px 0 0;font:600 12px/1.4 var(--csm-f-label);color:var(--jk)}
	/* A help (a results table's legend): a <dialog> in the browser's top layer, never cut by a short table or a scrolling box.
	   dvh: the height the screen shows now, so a phone's address bar, still shown on a short page, never hides its end (vh before, for an old browser). */
	.csm-widget dialog.help{box-sizing:border-box;width:min(480px,calc(100vw - 32px));max-height:min(84vh,680px);max-height:min(84dvh,680px);padding:0;border:1px solid var(--csm-line-strong);border-radius:14px;background:var(--csm-bg);color:var(--csm-text);box-shadow:0 18px 48px rgba(18,20,23,.28);font:600 13px/1.5 var(--csm-f-label);overflow:hidden;display:none;flex-direction:column}
	.csm-widget dialog.help[open]{display:flex}
	.csm-widget dialog.help::backdrop{background:rgba(18,20,23,.45)}
	.csm-widget dialog.help.fallback{position:fixed;inset:0;margin:auto;z-index:2147483000;height:fit-content}
	.csm-widget .help .h-h{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:16px 16px 14px 20px;border-bottom:1px solid var(--csm-line);border-top:3px solid var(--c,var(--jk))}
	.csm-widget .help .h-e{margin:0 0 4px;font:700 10px/1.3 var(--csm-f-label);letter-spacing:.1em;text-transform:uppercase;color:var(--csm-muted)}
	.csm-widget .help .h-h b{display:block;font:800 italic 22px/1.05 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink)}
	.csm-widget .help .h-x{flex:none;border:1px solid var(--csm-line);background:var(--csm-bg);width:34px;height:34px;border-radius:50%;font:600 20px/1 var(--csm-f-label);color:var(--csm-text);cursor:pointer}
	.csm-widget .help .h-x:hover{background:var(--csm-panel);color:var(--csm-ink)}
	.csm-widget .help .h-b{overflow-y:auto;overscroll-behavior:contain;padding:16px 20px 22px}
	/* a results table's legend (the season grid's, a class table's), in the same box */
	.csm-widget .hlegend .h-h{border-top-color:var(--csm-ink)}
	.csm-widget .hlegend .h-b>p{margin:0 0 12px;color:var(--csm-muted)}
	.csm-widget .hlegend dl{display:grid;gap:10px;margin:0}
	.csm-widget .hlegend dl div{display:grid;grid-template-columns:80px 1fr;gap:10px;align-items:start}
	.csm-widget .hlegend dt{display:flex;flex-wrap:wrap;align-items:center;gap:4px}
	.csm-widget .hlegend dd{margin:0}
	.csm-widget .hlegend .gc{min-width:22px;height:22px;font-size:11px;cursor:default}
	.csm-widget .hlegend .gcodes{margin:8px 0 0}
	/* a class table's samples: a cell as the table draws it, a column's name as its header */
	.csm-widget .hlegend .tcell{display:inline-flex;align-items:center;justify-content:center;min-width:30px;height:24px;padding:0 4px;box-sizing:border-box;border:1px solid var(--csm-line);border-radius:4px;font-size:13px;font-variant-numeric:tabular-nums;color:var(--csm-text)}
	.csm-widget .hlegend .tcol{font:800 10px/1.25 var(--csm-f-label);letter-spacing:.06em;text-transform:uppercase;color:var(--csm-muted);padding:5px 6px;border-radius:4px;background:var(--csm-panel)}
	/* on a phone: a panel that rises from the bottom, full width, its content scrolling, the × always in reach */
	@media (max-width:599px){
		.csm-widget dialog.help{width:100vw;max-width:100vw;max-height:88vh;max-height:88dvh;margin:auto 0 0;border-radius:16px 16px 0 0;border-bottom:0}
		.csm-widget dialog.help.fallback{inset:auto 0 0 0;margin:0}
		.csm-widget dialog.help[open]{animation:csm-help-up .22s ease-out}
		.csm-widget .help .h-h{padding:14px 14px 12px 16px}
		.csm-widget .help .h-b{padding:14px 16px calc(20px + env(safe-area-inset-bottom))}
		.csm-widget .hlegend dl div{grid-template-columns:70px 1fr}
	}
	@keyframes csm-help-up{from{transform:translateY(40px);opacity:.4}to{transform:none;opacity:1}}
	@media (prefers-reduced-motion:reduce){
		.csm-widget dialog.help[open]{animation:none}
	}
	@media (max-width:599px){
		.csm-widget .gc{min-width:28px;height:28px;font-size:13px}
		.csm-widget .grid th,.csm-widget .grid td{padding:2px 1px}
		}

	/* shared pieces: a rank in big figures, big points, a name, a line under it, a gap */
	.csm-widget .ord{font:800 italic 34px/1 var(--csm-f-display);color:var(--csm-ink);white-space:nowrap}
	.csm-widget .ord small{font-size:.5em}
	.csm-widget .ord.m1{color:var(--csm-gold)}
	.csm-widget .ord.m2{color:var(--csm-silver)}
	.csm-widget .ord.m3{color:var(--csm-bronze)}
	.csm-widget .bpts{font:800 italic 22px/1 var(--csm-f-display);color:var(--csm-ink);white-space:nowrap}
	.csm-widget .bpts small{font:700 11px var(--csm-f-label);font-style:normal;color:var(--csm-muted);margin-left:3px}
	.csm-widget .rname{font:700 14px/1.2 var(--csm-f-data);color:var(--csm-ink);text-align:left;overflow-wrap:anywhere}
	.csm-widget .subl{display:block;font:600 11px/1.35 var(--csm-f-label);color:var(--csm-muted);margin-top:2px}
	.csm-widget .gapt{font:600 11px var(--csm-f-label);color:var(--csm-muted);white-space:nowrap}
	.csm-widget .wbox{min-width:0;flex:1}
	/* the class chips on one line that scrolls, with the tabs' arrows (a mouse can't swipe) */
	.csm-widget .filters{display:flex;align-items:center;gap:8px;margin-bottom:14px}
	.csm-widget .fwrap{position:relative;flex:1;min-width:0}
	.csm-widget .fwrap .tarrow{bottom:0}
	.csm-widget .fscroll{display:flex;gap:6px;overflow-x:auto;scrollbar-width:none;padding:2px 0}
	.csm-widget .fscroll::-webkit-scrollbar{display:none}
	.csm-widget .fscroll .fchip,.csm-widget .filters .fclear{flex:0 0 auto}
	/* a class card: the leader in the spotlight, 2nd and 3rd with their gap */
	.csm-widget .kcard{position:relative;display:flex;flex-direction:column;border:1px solid var(--csm-line);border-top:3px solid var(--c);border-radius:var(--csm-radius);padding:12px 14px 10px;background:var(--csm-bg);cursor:pointer;transition:box-shadow .15s,transform .15s}
	.csm-widget .kcard:hover{box-shadow:0 6px 18px rgba(18,20,23,.08);transform:translateY(-1px)}
	.csm-widget .kcard:focus-visible{outline:2px solid var(--csm-ink);outline-offset:2px}
	.csm-widget .kcard header{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-bottom:10px}
	.csm-widget .kcard h3{margin:0;font:800 italic 18px/1 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink)}
	.csm-widget .kcard .kh{display:flex;align-items:center;min-width:0}
	.csm-widget .kc-lead{display:flex;align-items:center;gap:12px;padding:10px 12px;border-radius:8px;background:var(--csm-gold-bg);border-left:3px solid var(--csm-gold)}
	.csm-widget .kc-lead .ord{font-size:38px}
	.csm-widget .kc-lead .rname{font-size:15px}
	.csm-widget .kc-chase{list-style:none;margin:8px 0 0;padding:0}
	.csm-widget .kc-chase li{display:grid;grid-template-columns:44px minmax(0,1fr) auto 34px;align-items:center;gap:8px;padding:7px 2px 7px 12px;border-top:1px solid var(--csm-line)}
	.csm-widget .kc-chase li:first-child{border-top:0}
	.csm-widget .kc-chase .ord{font-size:20px}
	.csm-widget .kc-chase .rname{font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
	.csm-widget .cpts{font:700 13px var(--csm-f-data);color:var(--csm-ink);text-align:right;font-variant-numeric:tabular-nums}
	.csm-widget .kcard footer{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-top:auto;padding-top:10px;border-top:1px solid var(--csm-line)}
	.csm-widget .kcard footer .status{font-size:11px}
	.csm-widget .more-t{font:700 12px var(--csm-f-label);color:var(--csm-muted);white-space:nowrap;transition:color .15s}
	.csm-widget .kcard:hover .more-t{color:var(--csm-ink)}
	.csm-widget .kcard .notice{margin-top:4px}
	/* a class: the tabs' arrows, the podium */
	.csm-widget .tarrow{position:absolute;top:0;bottom:1px;z-index:2;width:36px;border:0;font:700 22px var(--csm-f-label);color:var(--csm-ink);cursor:pointer;display:none}
	.csm-widget .tarrow.l{left:0;background:linear-gradient(90deg,var(--csm-bg) 55%,rgba(255,255,255,0));text-align:left;padding-left:4px}
	.csm-widget .tarrow.r{right:0;background:linear-gradient(270deg,var(--csm-bg) 55%,rgba(255,255,255,0));text-align:right;padding-right:4px}
	.csm-widget .tabs-wrap.can-l .tarrow.l,.csm-widget .tabs-wrap.can-r .tarrow.r,.csm-widget .fwrap.can-l .tarrow.l,.csm-widget .fwrap.can-r .tarrow.r{display:block}
	.csm-widget .kpod{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;align-items:start}
	.csm-widget .kstep{display:flex;flex-direction:column;gap:2px;padding:14px 16px;border:1px solid var(--csm-line);border-top:3px solid var(--sc);border-radius:var(--csm-radius);background:var(--csm-bg)}
	.csm-widget .kstep.p1{order:2;--sc:var(--csm-gold);background:linear-gradient(var(--csm-gold-bg),var(--csm-bg) 70%);box-shadow:0 6px 18px rgba(18,20,23,.06)}
	.csm-widget .kstep.p2{order:1;--sc:var(--csm-silver);margin-top:18px}
	.csm-widget .kstep.p3{order:3;--sc:var(--csm-bronze);margin-top:30px}
	.csm-widget .kstep .ord{font-size:44px}
	.csm-widget .kstep.p1 .ord{font-size:52px}
	.csm-widget .kof{font:600 12px var(--csm-f-label);color:var(--csm-muted);margin:-2px 0 8px}
	.csm-widget .kstep .rname{font-size:16px}
	.csm-widget .kline{display:flex;justify-content:space-between;align-items:baseline;gap:8px;margin-top:10px;padding-top:8px;border-top:1px solid var(--csm-line)}
	.csm-widget .ktie{margin:6px 0 0;font:600 11px/1.4 var(--csm-f-label);color:var(--csm-muted)}
	@media (max-width:599px){
		.csm-widget .kpod{grid-template-columns:1fr;gap:8px}
		.csm-widget .kstep{display:grid;grid-template-columns:auto minmax(0,1fr) auto;column-gap:12px;align-items:center;margin-top:0!important;padding:10px 12px;border-top:1px solid var(--csm-line);border-left:3px solid var(--sc)}
		.csm-widget .kstep.p1{order:1}
		.csm-widget .kstep.p2{order:2}
		.csm-widget .kstep.p3{order:3}
		.csm-widget .kstep .ord,.csm-widget .kstep.p1 .ord{font-size:32px;grid-row:1/4}
		.csm-widget .kstep .kof{display:none}
		.csm-widget .kstep .rname,.csm-widget .kstep .subl{grid-column:2}
		.csm-widget .kstep .kline{grid-column:3;grid-row:1/4;flex-direction:column;align-items:flex-end;border:0;margin:0;padding:0;gap:2px}
		.csm-widget .kstep .ktie{grid-column:1/-1;grid-row:4}
		}
	/* the table: medal bars on 1 to 3, X in grey, the gap */
	.csm-widget tr.r1 td:first-child{box-shadow:inset 3px 0 0 var(--csm-gold)}
	.csm-widget tr.r2 td:first-child{box-shadow:inset 3px 0 0 var(--csm-silver)}
	.csm-widget tr.r3 td:first-child{box-shadow:inset 3px 0 0 var(--csm-bronze)}
	.csm-widget tbody tr:hover td{background:var(--csm-panel)}
	.csm-widget td.nm .who{font-weight:700}
	.csm-widget td .ab,.csm-widget .tcell .ab{color:var(--csm-line-strong);font-weight:600;font-size:12px}
	.csm-widget td.tot{font:800 italic 16px var(--csm-f-display)}
	.csm-widget .gapc{color:var(--csm-muted);font:600 12px var(--csm-f-data);font-variant-numeric:tabular-nums}
	.csm-widget th.gapc{font:800 10px/1.25 var(--csm-f-label);letter-spacing:.06em;cursor:default}
	/* the title race: badges, the strip, a class's panel */
	.csm-widget .tbadge{display:inline-flex;align-items:center;gap:4px;font:800 10px/1 var(--csm-f-label);letter-spacing:.06em;text-transform:uppercase;padding:5px 8px;border-radius:999px;white-space:nowrap}
	.csm-widget .tbadge.champ{background:var(--csm-gold);color:#fff}
	.csm-widget .tbadge.sure{background:var(--csm-gold-bg);color:#7a5f14;box-shadow:inset 0 0 0 1px var(--csm-gold)}
	.csm-widget .tbadge.open{background:#e8f4ec;color:var(--csm-live);box-shadow:inset 0 0 0 1px #b9dcc6}
	.csm-widget .tstrip{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:12px 24px;background:var(--csm-ink);color:#d9dce1;border-radius:var(--csm-radius);padding:14px 18px;margin-bottom:14px}
	.csm-widget .tstrip .k{display:block;font:800 11px var(--csm-f-label);letter-spacing:.12em;text-transform:uppercase;color:#9ba1ab;margin-bottom:4px}
	.csm-widget .tstrip>div>b{display:block;font:800 italic 24px/1.05 var(--csm-f-display);text-transform:uppercase;color:#fff}
	.csm-widget .tstrip>div>span{font:600 12px var(--csm-f-label);color:#9ba1ab}
	.csm-widget .tnums{display:flex;gap:22px}
	.csm-widget .tnums span{font:700 11px var(--csm-f-label);letter-spacing:.08em;text-transform:uppercase;color:#9ba1ab}
	.csm-widget .tnums b{display:block;font:800 italic 30px/1 var(--csm-f-display);color:#fff;letter-spacing:0}
	.csm-widget .tpanel{margin-top:16px;border:1px solid var(--csm-line);border-left:4px solid var(--csm-gold);border-radius:var(--csm-radius);padding:14px 16px;background:linear-gradient(90deg,var(--csm-gold-bg),var(--csm-bg) 55%)}
	.csm-widget .tpanel .th{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;margin-bottom:10px}
	.csm-widget .tpanel .k{font:800 italic 18px/1 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink)}
	.csm-widget .tleft{flex-basis:100%;font:600 12px var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .tleft b{color:var(--csm-ink)}
	@media (min-width:760px){
		.csm-widget .tleft{flex-basis:auto;margin-left:auto}
		}
	.csm-widget .tlead{display:flex;align-items:center;gap:12px;margin:0;font:600 14px/1.45 var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .tlead b,.csm-widget .tlead .rname{color:var(--csm-ink)}
	.csm-widget .tlist{list-style:none;margin:12px 0 0;padding:0;border-top:1px solid var(--csm-line)}
	.csm-widget .tlist li{display:grid;grid-template-columns:48px minmax(0,1fr) auto 64px;align-items:center;gap:10px;padding:8px 0;border-bottom:1px solid var(--csm-line)}
	.csm-widget .tlist .ord{font-size:22px}
	.csm-widget .tlist .subl{font-size:12px;color:var(--csm-text)}
	.csm-widget .tmax{font:800 italic 18px var(--csm-f-display);color:var(--csm-ink);text-align:right;white-space:nowrap}
	.csm-widget .tmax small{font:700 9px var(--csm-f-label);font-style:normal;letter-spacing:.08em;text-transform:uppercase;color:var(--csm-muted);margin-right:4px}
	.csm-widget .tlist .gapt{text-align:right}
	.csm-widget .tfoot{margin:10px 0 0;font:600 11px/1.4 var(--csm-f-label);color:var(--csm-muted)}
	@media (max-width:599px){
		.csm-widget .tlist li{grid-template-columns:40px minmax(0,1fr) auto}
		.csm-widget .tlist .gapt{display:none}
		}
	/* Courses: the races, a race's head, each class's final */
	.csm-widget .rmeta{display:flex;align-items:center;gap:6px}
	.csm-widget .rcards{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:12px;margin-top:12px}
	.csm-widget .rcard{border:1px solid var(--csm-line);border-radius:var(--csm-radius);padding:12px 14px}
	/* the final a disc of the Palmarès opened (user, 2026-10-07): marked, lit a moment */
	.csm-widget .rcard.hit{scroll-margin-top:12px;border-color:var(--csm-gold);box-shadow:0 0 0 2px var(--csm-gold);animation:csm-hit 2.4s ease-out}
	@keyframes csm-hit{0%,40%{background:var(--csm-gold-bg)}100%{background:transparent}}
	@media (prefers-reduced-motion:reduce){.csm-widget .rcard.hit{animation:none}}
	.csm-widget .rcard header{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:6px}
	.csm-widget .rcard h3{margin:0;font:800 italic 18px/1 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink)}
	.csm-widget .rcard header span{font:700 11px var(--csm-f-label);letter-spacing:.06em;text-transform:uppercase;color:var(--csm-muted)}
	.csm-widget .rlist{list-style:none;margin:0;padding:0}
	.csm-widget .rlist li{display:grid;grid-template-columns:28px minmax(0,1fr) auto 34px;align-items:center;gap:8px;padding:5px 0;border-top:1px solid var(--csm-line)}
	.csm-widget .rlist li:first-child{border-top:0}
	.csm-widget .rp{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;font:800 italic 13px var(--csm-f-display);color:var(--csm-ink);border:1px solid var(--csm-line-strong)}
	.csm-widget .rlist li.p1 .rp{background:var(--csm-gold);border-color:var(--csm-gold);color:#fff}
	.csm-widget .rlist li.p2 .rp{background:var(--csm-silver);border-color:var(--csm-silver);color:#fff}
	.csm-widget .rlist li.p3 .rp{background:var(--csm-bronze);border-color:var(--csm-bronze);color:#fff}
	.csm-widget .rlist .rname{font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
	.csm-widget .rlist li.p1 .rname{font-weight:800}
	.csm-widget .rno{font:600 11px var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .rpts{font:700 12px var(--csm-f-data);color:var(--csm-ink);text-align:right;font-variant-numeric:tabular-nums}
	.csm-widget .rpts.sp{color:var(--csm-dnf);font-size:11px}
	.csm-widget .rmore summary{cursor:pointer;font:700 12px var(--csm-f-label);color:var(--csm-ink);padding:6px 0 2px;list-style:none}
	.csm-widget .rmore summary::-webkit-details-marker{display:none}
	.csm-widget .rmore summary::before{content:"+ "}
	.csm-widget .rmore[open] summary::before{content:"\2212  "}
	.csm-widget .rnote,.csm-widget .rabs{margin:6px 0 0;font:600 12px/1.4 var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .stakes{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:10px}
	.csm-widget .stake{display:flex;flex-direction:column;align-items:flex-start;gap:4px;text-align:left;border:1px solid var(--csm-line);border-left:4px solid var(--csm-gold);border-radius:var(--csm-radius);background:var(--csm-bg);padding:12px 14px;cursor:pointer}
	.csm-widget .stake:hover{box-shadow:0 6px 18px rgba(18,20,23,.08)}
	.csm-widget .stake b{font:800 italic 18px/1 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink)}
	.csm-widget .stake span{font:600 12px var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .stake em{font:700 12px var(--csm-f-label);font-style:normal;color:var(--csm-ink)}
	.csm-widget .stake.sure{background:var(--csm-gold-bg)}
	@media (max-width:599px){
		.csm-widget .rcards{grid-template-columns:1fr}
		}

	/* ---- the Vérification page ---- */
	.csm-widget.csm-check .csm-inner{max-width:1120px;margin:0 auto;padding:24px 16px 48px}
	@media (min-width:600px){.csm-widget.csm-check .csm-inner{padding-inline:24px}}
	.csm-widget .csm-header h2{font:800 italic clamp(24px,4vw,32px)/1.1 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink);margin:0 0 8px}
	.csm-widget .csm-check-body{font:400 14px/1.45 var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .csm-check-season{font:800 italic 22px/1.1 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink);margin:28px 0 4px;padding-bottom:6px;border-bottom:2px solid var(--csm-ink)}
	.csm-widget .csm-check-body h5{margin:10px 0 2px;font:800 13px var(--csm-f-label)}
	.csm-widget .csm-check-body h6{margin:10px 0 2px;font:800 12px var(--csm-f-label);color:var(--csm-ink)}
	.csm-widget .csm-check-body ul{margin:0 0 6px;padding-left:20px}
	.csm-widget .csm-check-body li{margin:2px 0}
	/* The headings and lists of the cards keep these (as the approved mockup). */
	/* One card per problem (design/verification-mockup/mockup-v1.html). */
	.csm-widget .ck-bar{position:sticky;top:0;z-index:2;display:flex;flex-wrap:wrap;align-items:center;gap:8px 16px;margin:0 -16px;padding:10px 16px;background:rgba(255,255,255,.96);border-bottom:1px solid var(--csm-line)}
	.csm-widget .ck-counts{display:flex;flex-wrap:wrap;gap:6px}
	.csm-widget .ck-n{display:inline-flex;align-items:center;padding:3px 10px;border-radius:999px;font:800 12px/1.4 var(--csm-f-label);border:1px solid var(--csm-line-strong);color:var(--csm-ink);background:#fff}
	.csm-widget .ck-n.err{background:#fbeceb;border-color:#e7b9b4;color:var(--csm-dnf)}
	.csm-widget .ck-n.warn{background:#fdf4dc;border-color:#ecd28f;color:#8a5d00}
	.csm-widget .ck-n.ok{background:#e8f5ee;border-color:#b6dcc6;color:var(--csm-live)}
	.csm-widget .ck-n.done{font-variant-numeric:tabular-nums}
	.csm-widget .ck-nav{display:flex;flex-wrap:wrap;gap:6px;margin-left:auto}
	.csm-widget .ck-chip{display:inline-flex;align-items:center;gap:6px;padding:3px 4px 3px 10px;border-radius:999px;border:1px solid var(--csm-line-strong);font:700 12px/1.4 var(--csm-f-label);color:var(--csm-ink);text-decoration:none;background:#fff}
	.csm-widget .ck-chip span{min-width:20px;padding:0 6px;border-radius:999px;text-align:center;font-size:11px;background:var(--csm-panel)}
	.csm-widget .ck-chip.err span{background:var(--csm-dnf);color:#fff}
	.csm-widget .ck-chip.warn span{background:#e0a100;color:#fff}
	.csm-widget .ck-chip.ok span{background:#e8f5ee;color:var(--csm-live)}
	.csm-widget .ck-chip:hover{border-color:var(--csm-ink)}
	.csm-widget .ck-chip:focus-visible,.csm-widget .ck-done input:focus-visible,.csm-widget .ck-more summary:focus-visible{outline:2px solid var(--csm-ink);outline-offset:2px}
	.csm-widget .ck-intro{margin:12px 0 0;max-width:860px;font-size:13px;color:var(--csm-muted)}
	.csm-widget .ck-part{scroll-margin-top:64px}
	.csm-widget .ck-alert{margin:8px 0;padding:10px 12px;border-radius:8px;background:#fbeceb;color:var(--csm-dnf);font-weight:700}
	.csm-widget .ck-sec{margin:18px 0 6px;max-width:860px}
	.csm-widget .ck-sec h4{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 10px;margin:0 0 2px;font:800 15px/1.3 var(--csm-f-label);color:var(--csm-ink)}
	.csm-widget .ck-sc{display:inline-flex;gap:8px;font:700 12px var(--csm-f-label)}
	.csm-widget .ck-sc i{font-style:normal}
	.csm-widget .ck-sc .err{color:var(--csm-dnf)}
	.csm-widget .ck-sc .warn{color:#8a5d00}
	.csm-widget .ck-sechelp{margin:0 0 6px;font-size:12px;color:var(--csm-muted)}
	.csm-widget .ck-kind{margin:12px 0 6px;font:700 12px var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .ck-kind.err::before,.csm-widget .ck-kind.warn::before{content:"";display:inline-block;width:8px;height:8px;margin-right:6px;border-radius:50%;background:var(--csm-dnf);vertical-align:1px}
	.csm-widget .ck-kind.warn::before{background:#e0a100}
	.csm-widget .ck-sub{margin:14px 0 6px;font:800 italic 14px/1.2 var(--csm-f-display);text-transform:uppercase;letter-spacing:.02em;color:var(--csm-ink)}
	.csm-widget .ck-clean{margin:14px 0 0;max-width:860px;font-size:13px;color:var(--csm-text)}
	.csm-widget .ck-clean span{color:var(--csm-live);font-weight:800}
	/* A card. */
	.csm-widget .ck-card{position:relative;margin:0 0 10px;padding:12px 14px 14px 18px;border:1px solid var(--csm-line);border-radius:var(--csm-radius);background:#fff}
	.csm-widget .ck-card::before{content:"";position:absolute;left:0;top:0;bottom:0;width:5px;border-radius:var(--csm-radius) 0 0 var(--csm-radius);background:var(--csm-dnf)}
	.csm-widget .ck-card.warn::before{background:#e0a100}
	/* Réglé: folded down to its title and place, dimmed; « Afficher » opens it again, still checked. */
	.csm-widget .ck-card.is-done{background:var(--csm-panel)}
	.csm-widget .ck-card.is-done::before{background:var(--csm-live)}
	.csm-widget .ck-card.is-done:not(.is-open) .ck-body{display:none}
	.csm-widget .ck-card.is-done .ck-sev,.csm-widget .ck-card.is-done .ck-title,.csm-widget .ck-card.is-done .ck-where{opacity:.55}
	.csm-widget .ck-card.is-done .ck-title{text-decoration:line-through}
	.csm-widget .ck-toggle{margin-left:auto;padding:4px 10px;border:1px solid var(--csm-line-strong);border-radius:999px;background:#fff;font:700 12px var(--csm-f-label);color:var(--csm-ink);cursor:pointer}
	.csm-widget .ck-toggle[hidden]{display:none}
	.csm-widget .ck-toggle:hover{border-color:var(--csm-ink)}
	.csm-widget .ck-toggle:focus-visible{outline:2px solid var(--csm-ink);outline-offset:2px}
	.csm-widget .ck-toggle:not([hidden])+.ck-done{margin-left:0}
	.csm-widget .ck-top{display:flex;flex-wrap:wrap;align-items:center;gap:4px 10px}
	.csm-widget .ck-sev{padding:1px 8px;border-radius:4px;font:800 10px/1.6 var(--csm-f-label);text-transform:uppercase;letter-spacing:.06em;background:#fbeceb;color:var(--csm-dnf)}
	.csm-widget .ck-card.warn .ck-sev{background:#fdf4dc;color:#8a5d00}
	.csm-widget .ck-title{flex:1 1 220px;margin:0;font:800 15px/1.3 var(--csm-f-label);color:var(--csm-ink)}
	.csm-widget .ck-done{display:inline-flex;align-items:center;gap:6px;margin-left:auto;padding:4px 10px;border:1px solid var(--csm-line-strong);border-radius:999px;font:700 12px var(--csm-f-label);color:var(--csm-ink);cursor:pointer;user-select:none}
	.csm-widget .ck-done input{width:16px;height:16px;margin:0;accent-color:var(--csm-live);cursor:pointer}
	.csm-widget .ck-card.is-done .ck-done{border-color:var(--csm-live);color:var(--csm-live)}
	.csm-widget .ck-where{display:flex;flex-wrap:wrap;gap:2px 6px;margin:4px 0 0;font:700 12px var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .ck-where i{font-style:normal;color:var(--csm-muted)}
	.csm-widget .ck-what{margin:6px 0 0;font-size:13px;color:var(--csm-text)}
	.csm-widget .ck-ref{display:inline-block;padding:0 5px;border-radius:4px;background:var(--csm-panel);border:1px solid var(--csm-line);font:600 12px/1.5 var(--csm-f-data);color:var(--csm-ink);white-space:nowrap}
	/* The cells. */
	.csm-widget .ck-tw{margin:10px 0 0;overflow-x:auto}
	.csm-widget .ck-table{border-collapse:collapse;min-width:100%;font:400 13px/1.3 var(--csm-f-data);font-variant-numeric:tabular-nums}
	.csm-widget .ck-table th,.csm-widget .ck-table td{padding:6px 10px;border-bottom:1px solid var(--csm-line);text-align:left;vertical-align:top}
	.csm-widget .ck-table thead th{font:800 11px var(--csm-f-label);text-transform:uppercase;letter-spacing:.04em;color:var(--csm-muted);background:var(--csm-panel);border-bottom:0}
	.csm-widget .ck-table tbody th{font:700 13px/1.3 var(--csm-f-label);color:var(--csm-ink)}
	.csm-widget .ck-table small{display:block;margin-top:2px;font:500 11px var(--csm-f-data);color:var(--csm-muted)}
	.csm-widget .ck-table td b{font-weight:700;color:var(--csm-ink)}
	.csm-widget .ck-table td em{font-style:italic;font-weight:400;color:var(--csm-muted)}
	.csm-widget .ck-table td.odd{background:#fbeceb;box-shadow:inset 0 0 0 1px #e7b9b4}
	.csm-widget .ck-table td.odd b{color:var(--csm-dnf)}
	.csm-widget .ck-card.warn .ck-table td.odd{background:#fdf4dc;box-shadow:inset 0 0 0 1px #ecd28f}
	.csm-widget .ck-card.warn .ck-table td.odd b{color:#8a5d00}
	.csm-widget .ck-more{margin:6px 0 0}
	.csm-widget .ck-more summary{display:inline-block;cursor:pointer;font:700 12px var(--csm-f-label);color:var(--csm-ink);text-decoration:underline;text-underline-offset:2px}
	/* What it blocks, what to do. */
	.csm-widget .ck-boxes{display:grid;gap:8px;margin-top:10px}
	@media (min-width:760px){.csm-widget .ck-boxes{grid-template-columns:minmax(180px,1fr) 2.2fr}}
	.csm-widget .ck-box{padding:8px 12px;border-radius:8px;background:var(--csm-panel);font-size:13px}
	.csm-widget .ck-box>b{display:block;margin-bottom:2px;font:800 11px var(--csm-f-label);text-transform:uppercase;letter-spacing:.05em;color:var(--csm-muted)}
	.csm-widget .ck-box p{margin:0;color:var(--csm-ink)}
	.csm-widget .ck-box.fix{background:#eef6f1}
	.csm-widget .ck-box.fix>b{color:var(--csm-live)}
	.csm-widget .ck-box ol{margin:0;padding-left:20px}
	.csm-widget .ck-box ol>li{margin:4px 0;color:var(--csm-text)}
	.csm-widget .ck-when{font-weight:800;color:var(--csm-ink)}
	.csm-widget .ck-arrow{color:var(--csm-muted)}
	.csm-widget .ck-changes{margin:4px 0 0;padding-left:16px;columns:2 260px;column-gap:20px}
	.csm-widget .ck-changes li{margin:2px 0;break-inside:avoid}
	@media (max-width:599px){
		.csm-widget .ck-nav{margin-left:0}
		.csm-widget .ck-card{padding:10px 10px 12px 14px}
		.csm-widget .ck-table th,.csm-widget .ck-table td{padding:5px 7px}
	}
	/* An event's page (design/event-mockup/mockup-v1.html), and the ways in. */
	.csm-widget .cal-item[data-start],.csm-widget .race[data-start]{cursor:pointer;transition:background-color .15s,box-shadow .15s}
	.csm-widget .cal-item[data-start]:hover{background:#eef0f3}
	.csm-widget .cal-item.past[data-start]:hover{background:#e9ebee}
	.csm-widget .cal-item[data-start]:hover .ev{text-decoration:underline;text-underline-offset:3px}
	.csm-widget .cal-item[data-start]:focus-visible,.csm-widget .race[data-start]:focus-visible{outline:2px solid var(--csm-ink);outline-offset:-2px}
	.csm-widget .race[data-start]:hover{box-shadow:0 6px 18px rgba(18,20,23,.1)}
	.csm-widget .race .go{margin-top:auto;font:800 11px var(--csm-f-label);letter-spacing:.08em;text-transform:uppercase;color:var(--csm-muted)}
	.csm-widget .race.next .go{color:#9ba1ab}
	.csm-widget .race[data-start]:hover .go{color:var(--csm-ink)}
	.csm-widget .race.next[data-start]:hover .go{color:#fff}
	.csm-widget .evlink{border:0;background:none;padding:4px 0;font:700 13px var(--csm-f-label);color:var(--csm-ink);cursor:pointer;text-decoration:underline;text-decoration-color:var(--csm-line-strong);text-underline-offset:3px}
	.csm-widget .evlink:hover{text-decoration-color:currentColor}
	.csm-widget .gdetail .evlink{font-size:12px;margin-left:6px}
	/* The event's page. */
	.csm-widget .ehero{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:12px 24px;padding-bottom:18px;border-bottom:2px solid var(--csm-ink)}
	.csm-widget .ehero .eyebrow{margin:0;font:800 11px var(--csm-f-label);letter-spacing:.12em;text-transform:uppercase;color:var(--csm-muted)}
	.csm-widget .ehero .ek{color:var(--csm-ink)}
	.csm-widget .ehero .ek.live{color:var(--csm-live)}
	.csm-widget .ehero h2{font:800 italic clamp(30px,6vw,52px)/.95 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink);margin:6px 0 8px;letter-spacing:-.01em;text-wrap:balance}
	.csm-widget .ehero .ed{margin:0;display:flex;flex-wrap:wrap;align-items:center;gap:4px 8px;font:600 14px var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .echips{display:flex;gap:6px;flex-wrap:wrap}
	.csm-widget .echips .chip{border:0;cursor:pointer;font-size:12px;padding:6px 10px}
	.csm-widget .echips .chip:hover{filter:brightness(1.12)}
	.csm-widget .ewins{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:0 18px;border-top:1px solid var(--csm-line)}
	.csm-widget .ewins li{display:grid;grid-template-columns:86px 24px minmax(0,1fr);align-items:center;gap:8px;padding:6px 0;border-bottom:1px solid var(--csm-line)}
	.csm-widget .ewins .cl{font:800 11px var(--csm-f-label);letter-spacing:.06em;text-transform:uppercase;color:var(--csm-muted)}
	.csm-widget .ewins .rp{background:var(--csm-gold);border-color:var(--csm-gold);color:#fff}
	.csm-widget .ewins .rname{font-size:13px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:left}
	.csm-widget .rlist.multi li{grid-template-columns:28px minmax(0,1fr) auto auto}
	.csm-widget .evtags{display:flex;gap:4px;justify-content:flex-end;flex-wrap:wrap}
	.csm-widget .evtags .cc em{font-size:11px;color:var(--csm-ink);font-family:var(--csm-f-data);letter-spacing:0}
	.csm-widget .evtags .cc em.sp{color:var(--csm-dnf);font-size:10px}
	.csm-widget .egroup+.egroup{margin-top:16px}
	.csm-widget .egroup h3{display:flex;align-items:center;gap:8px;margin:0 0 8px;font:700 12px var(--csm-f-label);letter-spacing:.06em;text-transform:uppercase;color:var(--csm-muted)}
	.csm-widget .elinks{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:10px}
	.csm-widget .elink{display:flex;align-items:center;gap:12px;border:1px solid var(--csm-line);border-left:4px solid var(--c);border-radius:var(--csm-radius);padding:10px 14px}
	.csm-widget .elink img{width:44px;height:44px;object-fit:contain}
	.csm-widget .elink b{display:block;font:800 italic 16px/1.1 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink)}
	.csm-widget .elink span{display:flex;flex-wrap:wrap;gap:0 14px}
	@media (max-width:599px){
		.csm-widget .rlist.multi li{grid-template-columns:28px minmax(0,1fr) auto}
		.csm-widget .rlist.multi .rno{display:none}
		.csm-widget .ewins li{grid-template-columns:78px 24px minmax(0,1fr)}
	}
	.csm-widget .evtags .cbar{display:inline-block;width:4px;height:14px;border-radius:2px;background:var(--c);align-self:center}
	.csm-widget .evtags .rpts{min-width:26px}
	.csm-widget .etit li{grid-template-columns:86px 18px minmax(0,1fr)}
	.csm-widget .etit .cl{border:0;background:none;padding:0;text-align:left;cursor:pointer}
	.csm-widget .etit .cl:hover{color:var(--csm-ink);text-decoration:underline}
	.csm-widget .etit .star{color:var(--csm-gold);font-size:14px}
	.csm-widget .egroup{margin-bottom:14px}

	/* ---- the Palmarès and its show (the Soirée des champions): design/gala-mockup/mockup-v1.html ---- */

	/* The way in: the dashboard's Palmarès banner (public, or opened with the code). */
	.csm-widget .gl-entry{display:flex;align-items:center;gap:16px;width:100%;margin:0 0 18px;padding:16px 20px;border:0;border-radius:var(--csm-radius);cursor:pointer;text-align:left;color:#fff;background:radial-gradient(120% 180% at 0% 0%,#3a2f10 0%,#121417 55%);box-shadow:inset 0 0 0 1px rgba(184,149,46,.45)}
	.csm-widget .gl-entry b{display:block;font:800 italic 26px/1 var(--csm-f-display);text-transform:uppercase;letter-spacing:.01em;color:#f3d77a}
	.csm-widget .gl-entry small{display:block;margin-top:4px;font:600 13px var(--csm-f-label);color:#c9ccd2}
	.csm-widget .gl-entry-star{font-size:30px;color:var(--csm-gold);text-shadow:0 0 18px rgba(243,215,122,.6)}
	.csm-widget .gl-entry-go{margin-left:auto;font:800 24px var(--csm-f-label);color:#f3d77a;transition:transform .15s}
	.csm-widget .gl-entry:hover .gl-entry-go{transform:translateX(4px)}

	/* The Palmarès page. */
	.csm-widget .gl-hero{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:16px 24px;padding:26px 24px;margin:4px 0 16px;border-radius:14px;color:#fff;background:radial-gradient(90% 140% at 100% 0%,rgba(184,149,46,.35) 0%,rgba(18,20,23,0) 60%),#121417}
	.csm-widget .gl-hero .eyebrow{margin:0;font:800 11px var(--csm-f-label);letter-spacing:.14em;text-transform:uppercase;color:#f3d77a}
	.csm-widget .gl-hero h2{margin:8px 0 6px;font:800 italic clamp(34px,7vw,64px)/.92 var(--csm-f-display);text-transform:uppercase;letter-spacing:-.01em;color:#fff;text-wrap:balance}
	.csm-widget .gl-lede{margin:0;font:600 15px var(--csm-f-label);color:#c9ccd2}
	/* The private preview (before PUBLIC, 2.12.0; not in the mockups): « Aperçu privé… » with « Quitter », on the Palmarès and the dashboard; the form asking for the code. */
	.csm-widget .gl-quit{display:inline-flex;flex-wrap:wrap;align-items:center;gap:6px 10px;margin:10px 0 0;padding:4px 4px 4px 12px;border-radius:18px;background:#121417;box-shadow:inset 0 0 0 1px rgba(243,215,122,.35);font:700 12px var(--csm-f-label);color:#f3d77a}
	.csm-widget .view>.gl-quit{margin:0 0 10px}
	.csm-widget .gl-quit button{padding:5px 12px;border:0;border-radius:999px;cursor:pointer;background:rgba(243,215,122,.16);font:800 12px var(--csm-f-label);color:#fff}
	.csm-widget .gl-quit button:hover{background:rgba(243,215,122,.3)}
	.csm-widget .gl-gate form{display:flex;flex-wrap:wrap;align-items:flex-end;gap:12px;margin-top:18px}
	.csm-widget .gl-gate label{display:flex;flex-direction:column;gap:6px;font:800 11px var(--csm-f-label);letter-spacing:.14em;text-transform:uppercase;color:#f3d77a}
	.csm-widget .gl-gate input{width:min(260px,70vw);padding:12px 14px;border:1px solid rgba(243,215,122,.45);border-radius:10px;background:#1d2126;font:600 16px var(--csm-f-label);letter-spacing:normal;text-transform:none;color:#fff}
	.csm-widget .gl-gate input:focus-visible,.csm-widget .gl-quit button:focus-visible{outline:2px solid #f3d77a;outline-offset:2px}
	.csm-widget .gl-wrong{margin:12px 0 0;font:700 14px var(--csm-f-label);color:#ff8a80}
	.csm-widget .gl-playbox{display:flex;flex-direction:column;align-items:flex-end;gap:6px}
	.csm-widget .gl-play{display:inline-flex;align-items:center;gap:10px;padding:14px 22px;border:0;border-radius:999px;cursor:pointer;background:linear-gradient(180deg,#f3d77a,#B8952E);color:#121417;font:800 15px var(--csm-f-label);letter-spacing:.02em;box-shadow:0 8px 24px rgba(184,149,46,.35)}
	.csm-widget .gl-play:hover{filter:brightness(1.06)}
	.csm-widget .gl-playbox small{font:600 12px var(--csm-f-label);color:#9ba1ab}
	.csm-widget .gl-warn{margin:0 0 16px;padding:10px 14px;border-left:4px solid var(--csm-gold);background:var(--csm-gold-bg);border-radius:6px;font:600 13px var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .gl-figs{grid-template-columns:repeat(5,1fr)}
	.csm-widget .gl-jump{position:sticky;top:0;z-index:3;display:flex;gap:6px;flex-wrap:wrap;padding:10px 0;margin-bottom:6px;background:var(--csm-bg)}
	.csm-widget .gl-jump .chip{border:0;cursor:pointer;font-size:12px;padding:6px 12px}
	.csm-widget .gl-facts{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:12px}
	.csm-widget .gl-fact{border:1px solid var(--csm-line);border-radius:var(--csm-radius);padding:14px 16px;background:linear-gradient(180deg,var(--csm-gold-bg),#fff 70%)}
	.csm-widget .gl-fact .lab{font:800 11px var(--csm-f-label);letter-spacing:.08em;text-transform:uppercase;color:var(--csm-gold)}
	.csm-widget .gl-fv{display:flex;align-items:baseline;gap:6px;margin:4px 0 6px}
	.csm-widget .gl-fv b{font:800 italic 44px/1 var(--csm-f-display);color:var(--csm-ink)}
	.csm-widget .gl-fv small{font:700 13px var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .gl-fw{display:flex;flex-direction:column;align-items:flex-start;gap:2px}
	.csm-widget .gl-fw .who,.csm-widget .gl-fw span{font:700 14px var(--csm-f-label);color:var(--csm-ink)}
	.csm-widget .gl-flist{list-style:none;margin:8px 0 0;padding:0}
	.csm-widget .gl-flist li{display:flex;flex-direction:column;padding:4px 0;border-top:1px solid var(--csm-line)}
	.csm-widget .gl-flist .who{padding:0;text-align:left;font:700 13px var(--csm-f-label);color:var(--csm-ink)}
	.csm-widget .gl-flist em{font:600 11px var(--csm-f-label);font-style:normal;color:var(--csm-muted)}
	.csm-widget .gl-fact p{margin:6px 0 0;font:500 12px/1.4 var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .gl-champ{margin-top:34px;scroll-margin-top:56px}
	.csm-widget .gl-ch{display:flex;align-items:center;gap:14px;padding:12px 16px;border-radius:var(--csm-radius);background:var(--c);color:#fff;margin-bottom:12px}
	.csm-widget .gl-ch img{width:64px;height:40px;object-fit:contain;background:#fff;border-radius:6px;padding:3px}
	.csm-widget .gl-ch h3{margin:0;font:800 italic 24px/1 var(--csm-f-display);text-transform:uppercase}
	.csm-widget .gl-ch span{font:600 12px var(--csm-f-label);opacity:.85}
	.csm-widget .gl-ch .status{color:#fff;opacity:1}
	.csm-widget .gl-ch .status .spill{font:800 10px/1 var(--csm-f-label);opacity:1}
	.csm-widget .gl-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:12px}
	.csm-widget .gl-card{border:1px solid var(--csm-line);border-top:3px solid var(--c);border-radius:var(--csm-radius);padding:12px 14px}
	.csm-widget .gl-card header{display:flex;justify-content:space-between;align-items:baseline;gap:8px;margin-bottom:8px}
	.csm-widget .gl-card header b{font:800 italic 20px/1 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink)}
	.csm-widget .gl-card header span{font:600 11px var(--csm-f-label);color:var(--csm-muted);text-align:right}
	/* On the Palmarès a class's name, a championship's header and a duel's class open their page (user, 2026-10-07):
	   a « › », the underline on hover */
	.csm-widget .gl-go{border:0;background:none;padding:0;margin:0;font:inherit;color:inherit;text-transform:inherit;text-align:left;cursor:pointer;text-underline-offset:3px;text-decoration-thickness:2px}
	.csm-widget .gl-go:hover{text-decoration:underline}
	.csm-widget .gl-go:focus-visible,.csm-widget .gl-logo:focus-visible,.csm-widget .gl-pl:focus-visible{outline:2px solid currentColor;outline-offset:2px;border-radius:4px}
	.csm-widget .gl-arr{font-style:normal;margin-left:4px;opacity:.6}
	.csm-widget .gl-card .gl-arr{color:var(--c)}
	.csm-widget .gl-logo{flex:none;border:0;background:none;padding:0;cursor:pointer;line-height:0}
	.csm-widget .gl-duels .gl-go{align-self:flex-start}
	.csm-widget .gl-champion{display:flex;gap:10px;align-items:flex-start;padding:10px 12px;border-radius:8px;background:var(--csm-gold-bg)}
	.csm-widget .gl-star{font-size:22px;line-height:1;color:var(--csm-gold)}
	.csm-widget .gl-champion .who{display:block;padding:0;font:800 italic 20px/1.05 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink);text-align:left}
	.csm-widget .gl-meta{display:block;margin-top:4px;font:600 12px var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .gl-rest{list-style:none;margin:8px 0 0;padding:0}
	.csm-widget .gl-rest li{display:grid;grid-template-columns:24px minmax(0,1fr) auto;align-items:center;gap:8px;padding:4px 0;border-top:1px solid var(--csm-line)}
	.csm-widget .gl-rest .who{font-size:13px;font-weight:700;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
	.csm-widget .gl-rest em{font:600 12px var(--csm-f-data);font-style:normal;color:var(--csm-muted)}
	.csm-widget .gl-rest .rp.m2{background:var(--csm-silver);border-color:var(--csm-silver);color:#fff}
	.csm-widget .gl-rest .rp.m3{background:var(--csm-bronze);border-color:var(--csm-bronze);color:#fff}
	.csm-widget .gl-card .notice{margin:0}

	/* The champion's path: one disc per race, like the rider's season grid. */
	.csm-widget .gl-path{list-style:none;margin:8px 0 0;padding:0;display:flex;flex-wrap:wrap;gap:4px}
	.csm-widget .gl-path li{display:flex;flex-direction:column;align-items:center;gap:2px;width:34px}
	.csm-widget .gl-path b{display:grid;place-items:center;width:24px;height:24px;border-radius:50%;border:1.5px solid var(--csm-line-strong);font:700 11px var(--csm-f-data);color:var(--csm-ink);position:relative}
	.csm-widget .gl-path span{font:700 8px var(--csm-f-label);letter-spacing:.02em;text-transform:uppercase;color:var(--csm-muted);white-space:nowrap}
	.csm-widget .gl-path .p1 b{background:var(--csm-gold);border-color:var(--csm-gold);color:#fff}
	.csm-widget .gl-path .p2 b{background:var(--csm-silver);border-color:var(--csm-silver);color:#fff}
	.csm-widget .gl-path .p3 b{background:var(--csm-bronze);border-color:var(--csm-bronze);color:#fff}
	.csm-widget .gl-path .bad b{border-color:var(--csm-dnf);color:var(--csm-dnf);font-size:8px}
	.csm-widget .gl-path .abs b,.csm-widget .gl-path .out b{color:var(--csm-muted)}
	.csm-widget .gl-path .fut b{border-style:dotted}
	.csm-widget .gl-path .nr b{font-size:9px}
	/* on the page a disc opens its race's event (user, 2026-10-07); the show's discs are no links */
	.csm-widget .gl-pl{display:flex;flex-direction:column;align-items:center;gap:2px;width:100%;border:0;background:none;padding:0;color:inherit;cursor:pointer}
	.csm-widget .gl-pl:hover b{outline:2px solid var(--c);outline-offset:1px}
	.csm-widget .gl-pl:hover span{color:var(--csm-ink)}

	@media (max-width:599px){
		.csm-widget .gl-figs{grid-template-columns:repeat(3,1fr)}
		.csm-widget .gl-hero{padding:20px 16px}
		.csm-widget .gl-playbox{align-items:stretch;width:100%}
		.csm-widget .gl-play{justify-content:center}
		.csm-widget .gl-grid{grid-template-columns:1fr}
		.csm-widget .gl-entry b{font-size:22px}
	}

	/* ---------- The show: 1600 × 900, scaled to the screen ----------
	   In the browser's top layer (popover): above the theme's menu; the browser's popover box undone. Without it, z-index. */
	.csm-widget .gl-show{position:fixed;inset:0;z-index:2147483000;width:auto;height:auto;max-width:none;max-height:none;margin:0;padding:0;border:0;overflow:hidden;background:#07080a;color:#fff;font-family:var(--csm-f-label,system-ui);--c:#B8952E}
	.csm-widget .gl-show[hidden]{display:none}
	.csm-widget .gl-canvas{position:absolute;left:50%;top:50%;width:1600px;height:900px;transform-origin:50% 50%;cursor:pointer;overflow:hidden;
		background:radial-gradient(70% 90% at 50% 105%,color-mix(in srgb,var(--c) 55%,transparent) 0%,transparent 70%),radial-gradient(60% 60% at 50% -10%,rgba(255,255,255,.08),transparent 70%),#0b0c0f}
	.csm-widget .gl-canvas::before{content:"";position:absolute;inset:0;background:repeating-linear-gradient(115deg,rgba(255,255,255,.025) 0 2px,transparent 2px 22px);pointer-events:none}
	.csm-widget .sl{position:absolute;inset:0;padding:70px 90px;display:flex;flex-direction:column}
	.csm-widget .sl-kicker{font:800 18px var(--csm-f-label);letter-spacing:.22em;text-transform:uppercase;color:#f3d77a}
	.csm-widget .sl-season{font:700 italic 34px var(--csm-f-display);text-transform:uppercase;color:#c9ccd2;letter-spacing:.04em}
	.csm-widget .sl-sub{font:600 22px var(--csm-f-label);color:#9ba1ab}
	.csm-widget .sl-logo{display:inline-grid;place-items:center;background:#fff;border-radius:14px;padding:10px 14px;box-shadow:0 10px 30px rgba(0,0,0,.4)}
	.csm-widget .sl-logo img{height:64px;width:auto;max-width:170px;object-fit:contain}
	.csm-widget .sl-prov{position:absolute;top:34px;right:40px;padding:8px 14px;border-radius:999px;border:1px solid rgba(243,215,122,.5);font:700 16px var(--csm-f-label);color:#f3d77a}

	/* Opening and end. */
	.csm-widget .sl-open{align-items:center;justify-content:center;text-align:center;gap:18px}
	.csm-widget .sl-open h1{margin:0;padding:0 .2em;font:800 italic 150px/.9 var(--csm-f-display);text-transform:uppercase;letter-spacing:-.01em;background:linear-gradient(180deg,#fff 20%,#f3d77a 60%,#B8952E);-webkit-background-clip:text;background-clip:text;color:transparent;text-wrap:balance;animation:sl-rise .9s cubic-bezier(.2,.8,.2,1) both}
	.csm-widget .sl-logos{display:flex;gap:18px;margin-top:30px;flex-wrap:wrap;justify-content:center}
	.csm-widget .sl-logos .sl-logo img{height:52px}
	.csm-widget .sl-keys{position:absolute;bottom:46px;left:0;right:0;margin:0;font:600 17px var(--csm-f-label);color:#6c7280}
	.csm-widget .sl-open .sl-season{max-width:1100px;text-wrap:balance}

	/* The welcome screen (« Accueil »), before the opening: it loops by itself while the room fills. */
	.csm-widget .wl{align-items:center;text-align:center;padding:40px 90px 0;overflow:hidden}
	.csm-widget .wl-head,.csm-widget .wl-logos,.csm-widget .wl-classes,.csm-widget .wl-mid{position:relative}
	/* The background breathing: two lights in the first championships' colors drifting, the stripes sliding. */
	.csm-widget .wl .wl-amb{position:absolute;inset:0;pointer-events:none}
	.csm-widget .wl-blob{position:absolute;width:900px;height:900px;border-radius:50%;filter:blur(120px);opacity:.26;animation:wl-drift 30s ease-in-out infinite alternate}
	.csm-widget .wl-blob.a{left:-260px;top:-320px;background:var(--wa,#B8952E)}
	.csm-widget .wl-blob.b{right:-300px;bottom:-380px;background:var(--wb,var(--wa,#B8952E));animation-duration:38s;animation-direction:alternate-reverse}
	.csm-widget .wl-stripes{position:absolute;inset:-60px;background:repeating-linear-gradient(115deg,rgba(255,255,255,.03) 0 2px,transparent 2px 22px);animation:wl-slide 24s linear infinite}
	@keyframes wl-drift{0%{transform:translate(0,0) scale(1)}50%{transform:translate(220px,120px) scale(1.15)}100%{transform:translate(80px,260px) scale(.95)}}
	@keyframes wl-slide{to{transform:translateX(51px)}}
	/* A few gold sparks rising slowly. */
	.csm-widget .wl .wl-sparks{position:absolute;inset:0;pointer-events:none;overflow:hidden}
	.csm-widget .wl-sparks i{position:absolute;bottom:-20px;width:var(--s);height:var(--s);border-radius:50%;background:#f3d77a;box-shadow:0 0 12px 3px rgba(243,215,122,.6);opacity:0;animation:wl-spark linear infinite}
	@keyframes wl-spark{0%{opacity:0;transform:translateY(0)}15%{opacity:.85}80%{opacity:.5}100%{opacity:0;transform:translateY(-940px) translateX(40px)}}
	/* The title, its gold slowly shining (a little padding: the last italic letter stays whole). */
	.csm-widget .wl-head{display:flex;flex-direction:column;align-items:center;gap:6px}
	.csm-widget .wl h1{margin:0;padding:0 .14em .04em .06em;font:800 italic 76px/.95 var(--csm-f-display);text-transform:uppercase;letter-spacing:-.01em;background:linear-gradient(100deg,#fff 0%,#f3d77a 30%,#fff 45%,#f3d77a 60%,#B8952E 100%) 0 0/250% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;animation:wl-shine 9s ease-in-out infinite alternate}
	@keyframes wl-shine{to{background-position:100% 0}}
	.csm-widget .wl .sl-season{font-size:26px}
	/* The logos, lit one after the other in their championship's color (GALA ORDER). */
	.csm-widget .wl-logos{margin-top:14px;gap:22px}
	.csm-widget .wl-logos .sl-logo img{height:44px}
	.csm-widget .wl-logo{animation:wl-lit linear infinite both}
	/* Under the logos, the lit one's championship and its classes (GALA CLASS ORDER), each with its riders. */
	.csm-widget .wl-classes{width:1420px;height:64px;margin:12px 0 0;padding:0;list-style:none}
	/* A row too long glides to its end during its slot (wl-ckscroll, in the slide; user 2026-10-07): its auto margins keep it centered when it fits, at its start when it doesn't; the edges fade. */
	.csm-widget .wl-cl{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;gap:6px;white-space:nowrap;overflow:hidden;opacity:0;animation:wl-cls linear infinite both;-webkit-mask:linear-gradient(90deg,transparent,#000 40px,#000 calc(100% - 40px),transparent);mask:linear-gradient(90deg,transparent,#000 40px,#000 calc(100% - 40px),transparent)}
	.csm-widget .wl-cn{display:flex;align-items:baseline;gap:12px;font:600 15px var(--csm-f-label);color:#8a909a}
	.csm-widget .wl-cn b{font:800 21px var(--csm-f-label);color:#fff;padding-bottom:1px;border-bottom:3px solid var(--lc)}
	.csm-widget .wl-cn em{font-style:normal;letter-spacing:.04em}
	.csm-widget .wl-ck{display:flex;align-items:center;margin:0 auto;padding:0 40px;font-weight:600;font-family:var(--csm-f-label);color:#c9ccd2;animation:wl-ckscroll ease-in-out infinite both}
	.csm-widget .wl-ck>span+span::before{content:"·";margin:0 .5em;color:#6c7280}
	.csm-widget .wl-ck small{display:inline-grid;place-items:center;min-width:1.5em;margin-left:.35em;padding:0 .3em;border-radius:999px;background:rgba(255,255,255,.12);font:700 .78em var(--csm-f-data,var(--csm-f-label));color:#fff}
	/* The middle: the tour's map beside one card at a time (each race, then « Le saviez-vous ? »). */
	.csm-widget .wl-mid{display:flex;gap:40px;justify-content:center;width:1420px;height:270px;margin-top:16px}
	.csm-widget .wl.nomap .wl-mid{width:900px}
	.csm-widget .wl-map{flex:0 0 680px;margin:0;display:flex;flex-direction:column;gap:8px;text-align:left}
	.csm-widget .wl-map figcaption{display:flex;align-items:baseline;gap:14px;white-space:nowrap;font:600 18px var(--csm-f-label);color:#9ba1ab}
	.csm-widget .wl-map svg{width:680px;height:230px;border-radius:20px;background:radial-gradient(80% 90% at 50% 50%,rgba(255,255,255,.05),rgba(255,255,255,.015));border:1px solid rgba(255,255,255,.1);overflow:visible}
	.csm-widget .wl-segs line{stroke:#f3d77a;stroke-width:3;stroke-linecap:round;stroke-dasharray:1;stroke-dashoffset:1;opacity:.8}
	.csm-widget .wl-dot{fill:#3a3f48;stroke:rgba(255,255,255,.35);stroke-width:1.5}
	.csm-widget .wl-vis{fill:#f3d77a;opacity:0}
	.csm-widget .wl-cur{opacity:0}
	.csm-widget .wl-ring{fill:none;stroke:#f3d77a;stroke-width:3;transform-box:fill-box;transform-origin:center;animation:wl-pulse 1.6s ease-out infinite}
	.csm-widget .wl-code{font:800 13px var(--csm-f-label);letter-spacing:.08em;fill:#aab0ba;opacity:0;paint-order:stroke;stroke:#0b0c0f;stroke-width:4px;stroke-linejoin:round}
	.csm-widget .wl-km{font:700 14px var(--csm-f-data,var(--csm-f-label));fill:#f3d77a;opacity:0;paint-order:stroke;stroke:#0b0c0f;stroke-width:5px;stroke-linejoin:round}
	.csm-widget .wl-cur text{font:800 15px var(--csm-f-label);letter-spacing:.08em;fill:#fff;paint-order:stroke;stroke:#0b0c0f;stroke-width:6px;stroke-linejoin:round}
	@keyframes wl-pulse{from{transform:scale(.6);opacity:1}to{transform:scale(1.6);opacity:0}}
	.csm-widget .wl-deck{position:relative;flex:1;margin:0;padding:0;list-style:none}
	.csm-widget .wl-card{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;gap:8px;padding:26px 36px;border-radius:22px;text-align:left;background:linear-gradient(90deg,color-mix(in srgb,var(--rc,#B8952E) 28%,rgba(255,255,255,.04)),rgba(255,255,255,.04));border:1px solid rgba(255,255,255,.12);border-left:8px solid var(--rc,#B8952E);opacity:0;animation:wl-deck linear infinite both}
	.csm-widget .wl-rk{font:800 17px var(--csm-f-label);letter-spacing:.2em;text-transform:uppercase;color:#f3d77a}
	.csm-widget .wl-soon{font-style:normal;margin-left:10px;padding:2px 10px;border-radius:999px;border:1px solid rgba(243,215,122,.6);letter-spacing:.1em}
	.csm-widget .wl-rv{font:800 italic var(--vs,54px)/1.05 var(--csm-f-display);text-transform:uppercase;padding-right:.1em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
	.csm-widget .wl-rd{font:600 24px var(--csm-f-label);color:#c9ccd2}
	.csm-widget .wl-rn{font:600 21px var(--csm-f-label);color:#9ba1ab}
	.csm-widget .wl-rn b{color:#fff;font-weight:800}
	.csm-widget .wl-rl{position:absolute;top:22px;right:26px;display:flex;gap:10px}
	.csm-widget .wl-rl .sl-logo{padding:6px 10px;border-radius:10px}
	.csm-widget .wl-rl .sl-logo img{height:32px}
	.csm-widget .wl-rc{font:800 18px var(--csm-f-label);padding:6px 10px;border-radius:10px;background:rgba(255,255,255,.12)}
	.csm-widget .wl-know{align-items:center;text-align:center;background:linear-gradient(180deg,rgba(243,215,122,.14),rgba(255,255,255,.03));border-left-width:1px;border-color:rgba(243,215,122,.4)}
	.csm-widget .wl-kb{max-width:100%;white-space:nowrap;font:800 italic var(--ks,84px)/1 var(--csm-f-display);padding:0 .12em;background:linear-gradient(180deg,#fff 15%,#f3d77a 60%,#B8952E);-webkit-background-clip:text;background-clip:text;color:transparent}
	.csm-widget .wl-kt{max-width:580px;font:600 25px/1.25 var(--csm-f-label);color:#e6e8eb;text-wrap:balance}
	/* The riders' wall: every rider who took part, A to Z, slowly, with their championships' colors. */
	.csm-widget .wl .wl-wall{position:absolute;left:0;right:0;bottom:92px;display:flex;flex-direction:column;align-items:center;gap:10px}
	.csm-widget .wl-wk{font-size:16px;color:#9ba1ab}
	.csm-widget .wl-row{width:100%;overflow:hidden;-webkit-mask:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);mask:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent)}
	.csm-widget .wl-track{display:flex;width:max-content;animation:wl-scroll linear infinite}
	.csm-widget .wl-row.back .wl-track{animation-direction:reverse}
	.csm-widget .wl-copy{display:contents}
	.csm-widget .wl-chip{display:inline-flex;align-items:center;gap:10px;margin-right:38px;font:700 24px var(--csm-f-label);color:#e6e8eb;white-space:nowrap}
	.csm-widget .wl-chip b{display:inline-grid;place-items:center;min-width:46px;height:34px;padding:0 8px;border-radius:7px;background:#000;border:1.5px solid rgba(255,255,255,.55);font:800 20px var(--csm-f-data,var(--csm-f-label));color:#fff}
	.csm-widget .wl-chip i{width:10px;height:10px;border-radius:50%;margin-left:-4px;box-shadow:0 0 0 1.5px rgba(255,255,255,.4)}
	.csm-widget .wl-chip i:first-of-type{margin-left:2px}
	@keyframes wl-scroll{to{transform:translateX(-50%)}}
	/* Less motion: nothing moves (the rule of the show, below); the route drawn whole, the races listed, the wall still. */
	@media (prefers-reduced-motion:reduce){
		.csm-widget .wl-sparks,.csm-widget .wl-copy,.csm-widget .wl-classes,.csm-widget .wl-know,.csm-widget .wl-cur,.csm-widget .wl-km{display:none}
		.csm-widget .wl-segs line{stroke-dashoffset:0}
		.csm-widget .wl-vis,.csm-widget .wl-code{opacity:1}
		.csm-widget .wl-deck{display:grid;grid-template-columns:1fr 1fr;align-content:center;gap:6px 16px}
		.csm-widget .wl-race{position:static;opacity:1;padding:4px 12px;border-left-width:4px;border-radius:8px;gap:0;background:rgba(255,255,255,.04)}
		.csm-widget .wl-rk,.csm-widget .wl-rn,.csm-widget .wl-rl{display:none}
		.csm-widget .wl-rv{font-size:20px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
		.csm-widget .wl-rd{font-size:13px}
		.csm-widget .wl-track{width:auto;justify-content:center}
	}

	/* The season in figures. */
	.csm-widget .sl-stats{justify-content:center;gap:60px}
	.csm-widget .sl-stats h2,.csm-widget .sl-fact h2{margin:0;font:800 italic 72px/1 var(--csm-f-display);text-transform:uppercase;text-align:center}
	.csm-widget .sl-figs{display:grid;grid-template-columns:repeat(5,1fr);gap:24px}
	.csm-widget .sl-figs div{display:flex;flex-direction:column;align-items:center;gap:10px;padding:34px 10px;border-radius:18px;background:rgba(255,255,255,.05);box-shadow:inset 0 0 0 1px rgba(255,255,255,.08);animation:sl-rise .8s cubic-bezier(.2,.8,.2,1) both}
	.csm-widget .sl-figs div:nth-child(2){animation-delay:.1s}.csm-widget .sl-figs div:nth-child(3){animation-delay:.2s}.csm-widget .sl-figs div:nth-child(4){animation-delay:.3s}.csm-widget .sl-figs div:nth-child(5){animation-delay:.4s}
	.csm-widget .sl-figs b{font:800 italic 120px/1 var(--csm-f-display);color:#f3d77a;font-variant-numeric:tabular-nums}
	.csm-widget .sl-figs small{font:700 italic 26px/1 var(--csm-f-display);text-transform:uppercase;color:#9ba1ab;margin:-18px 0 -8px}
	.csm-widget .sl-figs span{font:800 18px var(--csm-f-label);letter-spacing:.14em;text-transform:uppercase;color:#c9ccd2}

	/* A championship. */
	.csm-widget .sl-champ{align-items:center;justify-content:center;text-align:center;gap:22px;background:radial-gradient(60% 70% at 50% 50%,color-mix(in srgb,var(--c) 45%,transparent),transparent 70%)}
	.csm-widget .sl-champ .sl-logo{padding:18px 26px;animation:sl-pop .7s cubic-bezier(.2,.8,.2,1) both}
	.csm-widget .sl-champ .sl-logo img{height:150px;max-width:420px}
	.csm-widget .sl-champ h2{margin:10px 0 0;font:800 italic 96px/.95 var(--csm-f-display);text-transform:uppercase;max-width:1350px;text-wrap:balance;animation:sl-rise .8s .15s cubic-bezier(.2,.8,.2,1) both}
	/* The section openers (Les podiums, Faits saillants, Nos champions): the championship's opening in gold, slow light
	   beams turning behind the title, the logos or the highlights' titles coming in one after the other. */
	.csm-widget .sl-section{--c:#B8952E;overflow:hidden}
	.csm-widget .sl-section::before{content:"";position:absolute;inset:-20%;background:conic-gradient(from 0deg at 50% 55%,transparent 0 40deg,rgba(243,215,122,.10) 60deg,transparent 80deg 160deg,rgba(243,215,122,.08) 180deg,transparent 200deg 280deg,rgba(243,215,122,.10) 300deg,transparent 320deg);animation:sl-beams 24s linear infinite;pointer-events:none}
	.csm-widget .sl-section>*{position:relative}
	.csm-widget .sl-section .sl-kicker{animation:sl-rise .7s cubic-bezier(.2,.8,.2,1) both}
	.csm-widget .sl-section h2{font-size:128px;padding:0 .2em;background:linear-gradient(180deg,#fff 25%,#f3d77a 65%,#B8952E);-webkit-background-clip:text;background-clip:text;color:transparent}
	.csm-widget .sl-section .sl-sub{font-size:28px;color:#c9ccd2;animation:sl-rise .8s .35s cubic-bezier(.2,.8,.2,1) both}
	.csm-widget .sl-section .sl-logos{margin-top:18px}
	.csm-widget .sl-section .sl-logos .sl-logo{animation:sl-pop .6s cubic-bezier(.2,.8,.2,1) both;animation-delay:calc(.7s + var(--i) * .12s)}
	.csm-widget .sl-section .sl-logos .sl-logo img{height:64px}
	.csm-widget .sl-teasers{list-style:none;margin:18px 0 0;padding:0;display:flex;flex-wrap:wrap;justify-content:center;gap:12px 14px;max-width:1300px}
	.csm-widget .sl-teasers li{font:700 22px var(--csm-f-label);color:#f3d77a;padding:8px 20px;border:1.5px solid rgba(243,215,122,.45);border-radius:999px;background:rgba(243,215,122,.06);animation:sl-pop .5s cubic-bezier(.2,.8,.2,1) both;animation-delay:calc(.7s + var(--i) * .1s)}

	/* A class: title, then 3rd, 2nd, the champion. */
	.csm-widget .sl-top{display:flex;align-items:center;gap:26px}
	.csm-widget .sl-top .sl-kicker{font-size:16px}
	.csm-widget .sl-class{margin:4px 0 2px;font:800 italic 92px/.9 var(--csm-f-display);text-transform:uppercase;letter-spacing:-.01em}
	.csm-widget .sl-stand{flex:1;display:grid;grid-template-columns:1fr 1.15fr 1fr;align-items:end;gap:26px;margin-top:10px}
	.csm-widget .sl-col{display:flex;flex-direction:column;justify-content:flex-end;height:100%}
	.csm-widget .sl-who{display:flex;flex-direction:column;align-items:center;text-align:center;gap:8px;padding:0 6px 18px;min-height:150px;justify-content:flex-end}
	.csm-widget .sl-name{font:800 italic 46px/.95 var(--csm-f-display);text-transform:uppercase;text-wrap:balance}
	.csm-widget .sl-col.r1 .sl-name{font-size:64px;padding:0 .2em;background:linear-gradient(180deg,#fff 30%,#f3d77a);-webkit-background-clip:text;background-clip:text;color:transparent}
	.csm-widget .sl-pts{font:600 20px var(--csm-f-label);color:#c9ccd2}
	.csm-widget .sl-pts b{font:800 italic 30px var(--csm-f-display);color:#fff}
	.csm-widget .sl-crown{padding:6px 14px;border-radius:999px;background:#f3d77a;color:#121417;font:800 16px var(--csm-f-label);letter-spacing:.12em;text-transform:uppercase;box-shadow:0 0 30px rgba(243,215,122,.55)}
	.csm-widget .sl-step{display:grid;place-items:start center;padding-top:14px;border-radius:14px 14px 0 0;background:rgba(255,255,255,.06);box-shadow:inset 0 0 0 1px rgba(255,255,255,.1);transition:background .5s,box-shadow .5s}
	.csm-widget .sl-step span{font:800 italic 70px/1 var(--csm-f-display);color:rgba(255,255,255,.18)}
	.csm-widget .sl-col.r1 .sl-step{height:250px}.csm-widget .sl-col.r2 .sl-step{height:180px}.csm-widget .sl-col.r3 .sl-step{height:130px}
	.csm-widget .sl-col.r1.on .sl-step{background:linear-gradient(180deg,#f3d77a,#B8952E);box-shadow:0 0 60px rgba(243,215,122,.45)}
	.csm-widget .sl-col.r2.on .sl-step{background:linear-gradient(180deg,#e6e6e6,#8C8C8C)}
	.csm-widget .sl-col.r3.on .sl-step{background:linear-gradient(180deg,#d9a47c,#A5673A)}
	.csm-widget .sl-col.on .sl-step span{color:rgba(18,20,23,.75)}
	.csm-widget .sl-podium.crowned .sl-col:not(.r1) .sl-who{opacity:.75}
	.csm-widget .sl-pathbox{display:flex;justify-content:center;margin-top:24px}
	.csm-widget .sl-notice{margin:auto;font:800 italic 54px var(--csm-f-display);text-transform:uppercase;color:#9ba1ab}

	/* The path on the show: bigger discs on the dark stage. */
	.csm-widget .gl-path.big{gap:10px;list-style:none;margin:0;padding:0;display:flex}
	.csm-widget .gl-path.big li{display:flex;flex-direction:column;align-items:center;gap:6px;width:auto;min-width:64px}
	.csm-widget .gl-path.big b{display:grid;place-items:center;width:48px;height:48px;border-radius:50%;border:2px solid rgba(255,255,255,.35);font:800 20px var(--csm-f-data);color:#fff;position:relative}
	.csm-widget .gl-path.big span{font:800 12px var(--csm-f-label);letter-spacing:.06em;text-transform:uppercase;color:#9ba1ab}
	.csm-widget .gl-path.big .p1 b{background:#B8952E;border-color:#f3d77a}
	.csm-widget .gl-path.big .p2 b{background:#8C8C8C;border-color:#d0d0d0}
	.csm-widget .gl-path.big .p3 b{background:#A5673A;border-color:#d9a47c}
	.csm-widget .gl-path.big .bad b{border-color:#e0685a;color:#ff8d80;font-size:13px}
	.csm-widget .gl-path.big .abs b,.csm-widget .gl-path.big .out b{color:#9ba1ab}
	.csm-widget .gl-path.big .fut b{border-style:dotted}
	.csm-widget .gl-path.big .nr b{font-size:15px}

	/* A highlight. */
	.csm-widget .sl-fact{align-items:center;justify-content:center;text-align:center;gap:14px}
	.csm-widget .sl-big{display:flex;align-items:baseline;gap:16px;animation:sl-pop .7s cubic-bezier(.2,.8,.2,1) both}
	.csm-widget .sl-big b{font:800 italic 240px/.9 var(--csm-f-display);color:#f3d77a;font-variant-numeric:tabular-nums;text-shadow:0 0 60px rgba(243,215,122,.35)}
	.csm-widget .sl-big small{font:800 italic 44px var(--csm-f-display);text-transform:uppercase;color:#c9ccd2}
	.csm-widget .sl-names{display:flex;flex-wrap:wrap;justify-content:center;gap:10px 28px;max-width:1350px;animation:sl-rise .8s .3s cubic-bezier(.2,.8,.2,1) both}
	.csm-widget .sl-names span{font:800 italic 50px/1 var(--csm-f-display);text-transform:uppercase}
	/* A highlight that counts riders: every one of them. */
	.csm-widget .sl-fact.has-list{justify-content:flex-start;padding-top:56px;gap:8px}
	.csm-widget .sl-fact.has-list h2{font-size:60px}
	.csm-widget .sl-fact.has-list .sl-big b{font-size:130px}
	.csm-widget .sl-fact.has-list .sl-big small{font-size:34px}
	.csm-widget .sl-fact.has-list .sl-detail{margin:0 0 10px}
	.csm-widget .sl-list{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:1fr;gap:6px 48px;width:100%;max-width:1000px;text-align:left;animation:sl-rise .8s .3s cubic-bezier(.2,.8,.2,1) both}
	.csm-widget .sl-list.cols{grid-template-columns:1fr 1fr;max-width:1400px}
	.csm-widget .sl-list li{display:flex;align-items:baseline;justify-content:space-between;gap:16px;padding:6px 0;border-bottom:1px solid rgba(255,255,255,.1)}
	.csm-widget .sl-list b{flex:none;white-space:nowrap;font:800 italic 34px/1.05 var(--csm-f-display);text-transform:uppercase}
	.csm-widget .sl-list span{font:700 18px var(--csm-f-label);color:#f3d77a;text-align:right}
	.csm-widget .sl-detail{margin:6px 0 0;max-width:1200px;font:600 22px/1.4 var(--csm-f-label);color:#9ba1ab;text-wrap:balance}

	/* The champions' wall. */
	.csm-widget .sl-wallgrid{list-style:none;margin:34px 0 0;padding:0;display:grid;grid-template-columns:repeat(3,1fr);grid-auto-rows:min-content;gap:10px 22px}
	.csm-widget .sl-wallgrid li{display:flex;flex-direction:column;gap:2px;padding:10px 16px;border-left:4px solid var(--c);background:rgba(255,255,255,.04);border-radius:0 10px 10px 0;animation:sl-rise .6s cubic-bezier(.2,.8,.2,1) both}
	.csm-widget .sl-wallgrid span{font:800 14px var(--csm-f-label);letter-spacing:.12em;text-transform:uppercase;color:#9ba1ab}
	.csm-widget .sl-wallgrid b{font:800 italic 30px/1 var(--csm-f-display);text-transform:uppercase}
	.csm-widget .sl-wall .sl-class{font-size:64px}

	/* Confetti, the champion's moment. */
	.csm-widget .sl-confetti{position:absolute;inset:0;pointer-events:none;overflow:hidden}
	.csm-widget .sl-confetti i{position:absolute;top:-30px;width:12px;height:22px;border-radius:2px;opacity:0;animation:sl-fall linear forwards}
	@keyframes sl-fall{0%{opacity:1;transform:translateY(0) rotate(var(--r))}100%{opacity:.9;transform:translateY(980px) rotate(calc(var(--r) + 720deg))}}
	@keyframes sl-rise{from{opacity:0;transform:translateY(40px)}to{opacity:1;transform:none}}
	@keyframes sl-pop{from{opacity:0;transform:scale(.8)}to{opacity:1;transform:none}}
	@keyframes sl-glow{from{filter:brightness(2.2)}to{filter:none}}
	@keyframes sl-beams{to{transform:rotate(360deg)}}

	/* The partners (SPONSORS), before « Merci ! » and looping after it: design/gala-sponsors-mockup/mockup-v1.html (Gratitude). */
	.csm-widget .sp{align-items:center;text-align:center;gap:22px;overflow:hidden;background:radial-gradient(900px 520px at 50% 18%,rgba(243,215,122,.20),transparent 70%)}
	.csm-widget .sp>*{position:relative;z-index:1}
	.csm-widget .sp .sl-kicker{flex:none}
	.csm-widget .sp-item{margin:0;display:flex;flex-direction:column;align-items:center;gap:10px;animation:sp-up .8s cubic-bezier(.2,.8,.2,1) both;animation-delay:calc(.15s + var(--i) * .09s)}
	.csm-widget .sp-plate{position:relative;display:grid;place-items:center;border-radius:16px;overflow:hidden;box-shadow:0 0 0 1px rgba(243,215,122,.55),0 0 46px rgba(243,215,122,.30),0 16px 40px rgba(0,0,0,.55)}
	.csm-widget .sp-plate::after{content:"";position:absolute;inset:0;background:linear-gradient(105deg,transparent 35%,rgba(255,255,255,.55) 50%,transparent 65%);transform:translateX(-120%);animation:sp-sheen 1.4s ease-out both;animation-delay:calc(.6s + var(--i,0) * .12s);mix-blend-mode:overlay;pointer-events:none}
	.csm-widget .sp-plate img{display:block;object-fit:contain}
	/* A partner without its logo file: its name, in big letters. */
	.csm-widget .sp-name-only{padding:0 8%;font:800 italic 1em/1.05 var(--csm-f-display);text-transform:uppercase;color:#121417;text-wrap:balance}
	.csm-widget .sp-solo .sp-name-only{font-size:72px}
	.csm-widget .sp-gold .sp-name-only{font-size:34px}
	.csm-widget .sp-silver .sp-name-only{font-size:22px}
	.csm-widget .sp-wall .sp-name-only,.csm-widget .sp-row .sp-name-only{font-size:17px}
	.csm-widget .sp-ghosts .sp-name-only{font-size:10px;padding:0 4px}
	.csm-widget .sp-item figcaption{display:flex;flex-direction:column;gap:2px;line-height:1.15}
	.csm-widget .sp-item figcaption b{font:700 22px var(--csm-f-display);text-transform:uppercase;color:#fff;letter-spacing:.02em}
	.csm-widget .sp-item figcaption small{font:600 15px var(--csm-f-label);color:#c9ccd2}
	.csm-widget .sp-cells{display:grid;grid-template-columns:repeat(var(--cols),auto);gap:26px 30px;justify-content:center;margin:auto 0}
	/* The opening: the title, the season, every logo small on its plate. */
	.csm-widget .sp-open{justify-content:center}
	.csm-widget .sp-openin{display:flex;flex-direction:column;align-items:center;gap:20px}
	.csm-widget .sp-open h1{margin:0;padding:0 .08em;font:800 italic 118px/.95 var(--csm-f-display);text-transform:uppercase;background:linear-gradient(180deg,#fff 20%,#f3d77a 62%,#B8952E);-webkit-background-clip:text;background-clip:text;color:transparent;animation:sl-rise 1s cubic-bezier(.2,.8,.2,1) both}
	.csm-widget .sp-ghosts{display:flex;flex-wrap:wrap;justify-content:center;gap:10px 14px;max-width:1300px;margin-top:18px}
	.csm-widget .sp-ghosts span{display:grid;place-items:center;width:120px;height:60px;border-radius:8px;overflow:hidden;opacity:0;animation:sp-ghost .5s ease both;animation-delay:calc(1.4s + var(--i) * .05s)}
	.csm-widget .sp-ghosts img{display:block;object-fit:contain}
	/* A Platinum, alone: the largest plate of the show. */
	.csm-widget .sp-solo{justify-content:center;gap:24px}
	.csm-widget .sp-solo .sp-plate{border-radius:22px}
	.csm-widget .sp-solo .sp-item{animation:sp-solo 1.2s cubic-bezier(.2,.8,.2,1) both}
	.csm-widget .sp-name{margin:0;font:800 italic 64px/1 var(--csm-f-display);text-transform:uppercase;color:#fff;letter-spacing:.01em}
	.csm-widget .sp-since{font:700 24px var(--csm-f-label);color:#f3d77a;letter-spacing:.04em}
	.csm-widget .sp-solo .sp-name,.csm-widget .sp-solo .sp-since{animation:sp-up .8s .7s cubic-bezier(.2,.8,.2,1) both}
	.csm-widget .sp-silver .sp-item figcaption b{font-size:18px}
	.csm-widget .sp-silver .sp-item figcaption small{font-size:13px}
	/* The wall: Bronze, then À la carte, each with its title. */
	.csm-widget .sp-wall{justify-content:center;gap:14px}
	.csm-widget .sp-block{display:flex;flex-direction:column;align-items:center;gap:10px}
	.csm-widget .sp-block h3{margin:0;font:800 15px var(--csm-f-label);letter-spacing:.24em;text-transform:uppercase;color:#c9ccd2}
	.csm-widget .sp-wall .sp-cells{gap:12px 14px;margin:0}
	.csm-widget .sp-wall .sp-plate{border-radius:10px}
	.csm-widget .sp-wall .sp-item{gap:5px}
	.csm-widget .sp-wall .sp-item figcaption b{max-width:184px;overflow:hidden;font:600 12px var(--csm-f-label);text-transform:none;letter-spacing:0;color:#c9ccd2;white-space:nowrap;text-overflow:ellipsis}
	/* The loop, after « Merci ! »: two rows going opposite ways, without end. */
	.csm-widget .sp-loop{justify-content:space-between;padding:56px 0 44px}
	.csm-widget .sp-loophead{display:flex;flex-direction:column;align-items:center;gap:10px}
	.csm-widget .sp-loophead h2{margin:0;font:800 italic 70px/1 var(--csm-f-display);text-transform:uppercase;color:#fff}
	.csm-widget .sp-row{width:100%;overflow:hidden;-webkit-mask:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);mask:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent)}
	.csm-widget .sp-track{display:flex;gap:26px;width:max-content;padding:30px 13px;animation:sp-marquee 70s linear infinite}
	.csm-widget .sp-right .sp-track{animation-direction:reverse;animation-duration:90s}
	.csm-widget .sp-row .sp-item{animation:none}
	.csm-widget .sp-club{display:flex;align-items:center;gap:22px}
	.csm-widget .sp-club>span{font:800 16px var(--csm-f-label);letter-spacing:.22em;text-transform:uppercase;color:#c9ccd2}
	.csm-widget .sp-club .sl-logos{margin:0}
	.csm-widget .sp-club .sl-logo img{height:34px}
	/* Gold sparks rising. */
	.csm-widget .sp-sparks{position:absolute;inset:0;z-index:0;pointer-events:none}
	.csm-widget .sp-sparks i{position:absolute;bottom:-10px;width:5px;height:5px;border-radius:50%;background:#f3d77a;box-shadow:0 0 10px 2px rgba(243,215,122,.8);opacity:0;animation:sp-spark linear infinite}
	@keyframes sp-ghost{to{opacity:.9}}
	@keyframes sp-spark{0%{opacity:0;transform:translateY(0)}15%{opacity:.9}100%{opacity:0;transform:translateY(-760px)}}
	@keyframes sp-sheen{to{transform:translateX(120%)}}
	@keyframes sp-up{from{opacity:0;transform:translateY(28px)}to{opacity:1;transform:none}}
	@keyframes sp-solo{from{opacity:0;transform:scale(.88);filter:brightness(1.8)}to{opacity:1;transform:none;filter:none}}
	@keyframes sp-marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
	@media (prefers-reduced-motion:reduce){
		.csm-widget .sp-ghosts span{opacity:.9}
		.csm-widget .sp-sparks{display:none}
	}

	/* The bottom bar (what's on, where we are) and the tools. */
	.csm-widget .gl-bar{position:absolute;left:0;right:0;bottom:0;display:flex;justify-content:space-between;align-items:center;gap:12px;padding:8px 16px 10px;font:700 12px var(--csm-f-label);letter-spacing:.06em;text-transform:uppercase;color:#4d525c}
	.csm-widget .gl-bar i{position:absolute;left:0;bottom:0;height:3px;background:var(--c);transition:width .3s}
	.csm-widget .gl-tools{position:absolute;top:10px;right:12px;display:flex;gap:6px;opacity:.25;transition:opacity .2s}
	.csm-widget .gl-show:hover .gl-tools{opacity:1}
	.csm-widget .gl-tools button{width:36px;height:36px;border:0;border-radius:8px;background:rgba(255,255,255,.1);color:#fff;font:700 18px system-ui;cursor:pointer}
	.csm-widget .gl-tools button:hover{background:rgba(255,255,255,.2)}

	@media (prefers-reduced-motion:reduce){
		.csm-widget .gl-show *,.csm-widget .gl-show *::before{animation:none!important;transition:none!important}
		.csm-widget .sl-confetti{display:none}
	}

	/* The tightest titles: one duel per line, and what separated them (art. 19.4). */
	.csm-widget .sl-duels{list-style:none;margin:6px 0 0;padding:0;display:flex;flex-direction:column;gap:18px;width:100%;max-width:1300px;animation:sl-rise .8s .3s cubic-bezier(.2,.8,.2,1) both}
	.csm-widget .sl-duels li{display:flex;flex-direction:column;align-items:center;gap:8px;padding:18px 24px;border-radius:16px;background:rgba(255,255,255,.05);box-shadow:inset 0 0 0 1px rgba(255,255,255,.1)}
	.csm-widget .sl-dcls{font:800 18px var(--csm-f-label);letter-spacing:.16em;text-transform:uppercase;color:#f3d77a}
	.csm-widget .sl-dpair{display:flex;align-items:baseline;justify-content:center;gap:22px}
	.csm-widget .sl-dpair>span{white-space:nowrap}
	.csm-widget .sl-dpair b{font:800 italic 38px/1 var(--csm-f-display);text-transform:uppercase}
	.csm-widget .sl-dpair .win b{color:#f3d77a}
	.csm-widget .sl-dpair small{margin-left:10px;font:700 22px var(--csm-f-label);color:#c9ccd2}
	.csm-widget .sl-dpair .vs{font:700 italic 24px var(--csm-f-display);text-transform:uppercase;color:#6c7280}
	.csm-widget .sl-dwhy{font:600 21px var(--csm-f-label);color:#c9ccd2;text-align:center}
	.csm-widget .gl-duels li{gap:2px;padding:6px 0}
	.csm-widget .gl-duels .lab{font:800 11px var(--csm-f-label);letter-spacing:.08em;text-transform:uppercase;color:var(--csm-gold)}
	.csm-widget .gl-duels span{font:600 12px var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .gl-duels .who{display:inline;font-size:13px}
	.csm-widget .gl-duels b{font:700 12px var(--csm-f-data);color:var(--csm-ink)}

	/* A class in one click: the 3rd, the 2nd, then the champion come in by themselves, each at its --d. */
	.csm-widget .sl-podium.auto .sl-col.on .sl-who{animation:sl-rise .7s var(--d) cubic-bezier(.2,.8,.2,1) both}
	.csm-widget .sl-podium.auto .sl-col.on .sl-step{animation:sl-unlit var(--d) steps(1,end),sl-glow 1s var(--d) ease-out}
	.csm-widget .sl-podium.auto .sl-col.on .sl-step span{animation:sl-unlit-n var(--d) steps(1,end)}
	.csm-widget .sl-podium.auto.crowned .sl-col:not(.r1) .sl-who{animation:sl-rise .7s var(--d) cubic-bezier(.2,.8,.2,1) both,sl-dim .6s var(--dc) forwards}
	.csm-widget .sl-podium.auto .sl-stand{--dc:var(--d)}
	.csm-widget .sl-podium.auto .sl-pathbox{animation:sl-rise .8s calc(var(--d) + .5s) cubic-bezier(.2,.8,.2,1) both}
	@keyframes sl-unlit{from,to{background:rgba(255,255,255,.06);box-shadow:inset 0 0 0 1px rgba(255,255,255,.1)}}
	@keyframes sl-unlit-n{from,to{color:rgba(255,255,255,.18)}}
	@keyframes sl-dim{from{opacity:1}to{opacity:.75}}
	/* Switched to « Auto » halfway: the places already in stay put (the others dim when the champion comes in). */
	.csm-widget .sl-podium.auto .sl-col.in .sl-who,.csm-widget .sl-podium.auto .sl-col.in .sl-step,.csm-widget .sl-podium.auto .sl-col.in .sl-step span{animation:none}
	.csm-widget .sl-podium.auto.crowned .sl-col.in:not(.r1) .sl-who{animation:sl-dim .6s var(--dc) both}

	/* A class one click per place (the default): a place waits on its unlit step, then comes in on its click; the champion brings the path. */
	.csm-widget .sl-podium.clicks .sl-col:not(.on) .sl-who{visibility:hidden}
	.csm-widget .sl-podium.clicks .sl-col.on:not(.in) .sl-who{animation:sl-rise .7s cubic-bezier(.2,.8,.2,1) both}
	.csm-widget .sl-podium.clicks .sl-col.on:not(.in) .sl-step{animation:sl-glow 1s ease-out}
	.csm-widget .sl-podium.clicks.crowned .sl-col.in:not(.r1) .sl-who{animation:sl-dim .6s forwards}
	.csm-widget .sl-podium.clicks.crowned .sl-col.on:not(.in):not(.r1) .sl-who{animation:sl-rise .7s cubic-bezier(.2,.8,.2,1) both,sl-dim .6s forwards}
	.csm-widget .sl-podium.clicks:not(.crowned) .sl-pathbox{visibility:hidden}
	.csm-widget .sl-podium.clicks.crowned .sl-pathbox{animation:sl-rise .8s .5s cubic-bezier(.2,.8,.2,1) both}

	/* The tools' switch: one click per place, or « Auto » (A). */
	.csm-widget .gl-tools .gl-mode{width:auto;padding:0 10px;font:700 13px var(--csm-f-label);letter-spacing:.06em;text-transform:uppercase;white-space:nowrap}
	.csm-widget .gl-tools .gl-mode[aria-pressed=true]{background:rgba(243,215,122,.28);color:#f3d77a}

	/* The bottom bar: ‹ where we are · N / total › (the arrows on a touch screen only). */
	.csm-widget .gl-where{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
	.csm-widget .gl-nav{display:none;flex:none;width:44px;height:44px;border:0;border-radius:50%;background:rgba(255,255,255,.1);color:#fff;font:700 28px/1 system-ui;cursor:pointer}
	.csm-widget .gl-nav:disabled{opacity:.3;cursor:default}
	.csm-widget .sl-keys.touch{display:none}
	.csm-widget .gl-rotate{display:none;position:absolute;left:0;right:0;top:calc(50% + 28.125vw + 18px);margin:0;text-align:center;font:600 14px var(--csm-f-label);color:#6c7280;pointer-events:none}
	@media (orientation:portrait) and (max-width:699px){.csm-widget .gl-rotate{display:block}}
	/* A pinch zooms (one finger stays the show's: a tap, a swipe); zoomed, one finger moves the view. */
	.csm-widget .gl-show{touch-action:pinch-zoom;-webkit-user-select:none;user-select:none}
	.csm-widget .gl-show.zoomed{touch-action:pan-x pan-y pinch-zoom}

	/* The table of contents (M, G or ☰): one block per part, a click goes there. */
	.csm-widget .gl-toc{position:absolute;inset:0;display:flex;flex-direction:column;background:#0b0c0f;touch-action:pan-y}
	.csm-widget .gl-toc header{display:flex;align-items:center;gap:16px;padding:12px 16px 12px 20px;border-bottom:1px solid #22252b}
	.csm-widget .gl-toc header b{font:800 italic 22px var(--csm-f-display);text-transform:uppercase;color:#f3d77a}
	.csm-widget .gl-thelp{flex:1;font:600 12px var(--csm-f-label);color:#6c7280}
	.csm-widget .gl-toc header button{flex:none;width:44px;height:44px;border:0;border-radius:50%;background:rgba(255,255,255,.08);color:#fff;font:700 24px/1 system-ui;cursor:pointer}
	.csm-widget .gl-tbody{flex:1;overflow:auto;padding:16px 20px 40px;display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));grid-auto-rows:max-content;gap:14px;align-content:start;overscroll-behavior:contain}
	.csm-widget .gl-tb{border-radius:12px;background:#15171b;box-shadow:inset 0 3px 0 var(--c),inset 0 0 0 1px #22252b}
	.csm-widget .gl-tb.here{box-shadow:inset 0 3px 0 var(--c),inset 0 0 0 1px color-mix(in srgb,var(--c) 60%,#22252b)}
	.csm-widget .gl-tb summary{display:flex;align-items:center;gap:10px;min-height:44px;padding:12px 14px 8px;cursor:pointer;list-style:none}
	.csm-widget .gl-tb summary::-webkit-details-marker{display:none}
	.csm-widget .gl-tk{flex:none;display:grid;place-items:center;min-width:22px;height:22px;padding:0 4px;border-radius:5px;background:rgba(255,255,255,.1);font:800 12px var(--csm-f-data);color:#c9ccd2}
	.csm-widget .gl-tk:empty{display:none}
	.csm-widget .gl-tl{flex:none;width:44px;height:28px;object-fit:contain;background:#fff;border-radius:5px;padding:2px}
	.csm-widget .gl-th{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
	.csm-widget .gl-tt{font:800 italic 17px/1.05 var(--csm-f-display);text-transform:uppercase;color:#fff;overflow-wrap:anywhere}
	.csm-widget .gl-tn{font:600 11px var(--csm-f-label);color:#6c7280}
	.csm-widget .gl-tis{display:flex;flex-wrap:wrap;gap:6px;padding:4px 14px 14px}
	.csm-widget .gl-tsub{flex-basis:100%;margin:6px 0 0;font:800 11px var(--csm-f-label);letter-spacing:.1em;text-transform:uppercase;color:#9ba1ab}
	.csm-widget .gl-ti{min-height:36px;padding:6px 11px;border:1px solid #2a2e35;border-radius:8px;background:#1b1e23;color:#e6e7ea;font:700 13px var(--csm-f-label);cursor:pointer;text-align:left}
	.csm-widget .gl-ti.wide{flex-basis:100%;border-left:3px solid var(--c)}
	.csm-widget .gl-ti:hover{background:#252930}
	.csm-widget .gl-ti.seen{color:#7d838e}
	.csm-widget .gl-ti.seen::before{content:"✓ ";color:var(--c)}
	.csm-widget .gl-ti[aria-current]{background:#f3d77a;border-color:#f3d77a;color:#121417}
	.csm-widget .gl-ti[aria-current]::before{content:"▶ ";color:#121417}
	.csm-widget .gl-tb :focus-visible,.csm-widget .gl-toc header button:focus-visible{outline:2px solid #f3d77a;outline-offset:2px}

	/* A phone or a tablet: bigger targets, the arrows, the tools always visible, no key help. */
	@media (pointer:coarse){
		.csm-widget .gl-nav{display:block}
		.csm-widget .sl-keys{display:none}
		.csm-widget .sl-keys.touch{display:block}
		.csm-widget .gl-tools{opacity:.7}
		.csm-widget .gl-tools button{width:44px;height:44px}
		.csm-widget .gl-thelp{display:none}
		.csm-widget .gl-ti{min-height:44px;padding:8px 13px;font-size:14px}
	}
	@media (max-width:699px){
		.csm-widget .gl-tbody{grid-template-columns:1fr;padding:12px 12px 40px;gap:10px}
		.csm-widget .gl-bar{padding:6px 10px 8px}
	}
	/* ---- the stakes as a rider sees them: the objective, the followed rider ---- */
	/* The class chips' line. */
	.csm-widget .fline{display:flex;flex-wrap:wrap;align-items:center;gap:10px 16px;margin-bottom:14px}
	.csm-widget .fline .filters{flex:1 1 320px;min-width:0;margin:0}
	/* A rider's objective (rider page, « Vous suivez »). */
	.csm-widget .obj{margin:0;padding:6px 10px;border-radius:8px;font:700 12px/1.35 var(--csm-f-label);text-wrap:pretty}
	.csm-widget .obj.champ{background:var(--csm-gold);color:#fff}
	.csm-widget .obj.sure{background:var(--csm-gold-bg);color:#7a5f14;box-shadow:inset 0 0 0 1px var(--csm-gold)}
	.csm-widget .obj.lead{background:#e8f4ec;color:var(--csm-live);box-shadow:inset 0 0 0 1px #b9dcc6}
	.csm-widget .obj.open{background:var(--csm-bg);color:var(--csm-ink);box-shadow:inset 0 0 0 1px var(--csm-line-strong)}
	.csm-widget .obj.out,.csm-widget .obj.res{background:var(--csm-panel);color:var(--csm-muted)}
	.csm-widget .obj.res b{color:var(--csm-ink)}
	.csm-widget .ecard .obj{grid-column:1/-1;margin-top:6px}

	/* « ☆ Ajouter aux favoris » beside « Copier le lien ». */
	.csm-widget .hero-r .follow{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--csm-line-strong);background:var(--csm-bg);border-radius:999px;padding:7px 12px;font:700 12px var(--csm-f-label);color:var(--csm-ink);cursor:pointer}
	.csm-widget .hero-r .follow:hover{border-color:var(--csm-ink)}
	.csm-widget .hero-r .follow[aria-pressed="true"]{background:var(--csm-gold-bg);border-color:var(--csm-gold);color:#7a5f14}
	.csm-widget .hero-r .follow:focus-visible{outline:2px solid var(--csm-ink);outline-offset:2px}

	/* ---- A new version while the page is open (src/site/update.ts; frozen mockup design/update-mockup/). The
	   versions' line, the banner, the veil and the live region sit beside the root in the shadow root (the root is
	   redrawn at each view), like CSM UI's « Haut de page »: they take .csm-ui for the look, so they are not under
	   .csm-widget (a deliberate exception). ---- */
	.csm-ui.upd-ver{display:block;margin:0;padding:20px 16px 24px;text-align:center;font:600 12px/1.4 var(--csm-f-label);letter-spacing:.02em;color:var(--csm-muted)}
	.csm-ui.upd-ver[hidden]{display:none}
	.upd-live{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
	.csm-ui.upd{position:fixed;z-index:41;left:max(16px,env(safe-area-inset-left));right:max(76px,env(safe-area-inset-right));bottom:max(16px,env(safe-area-inset-bottom));display:flex;flex-wrap:wrap;align-items:center;gap:12px;padding:12px 12px 12px 14px;background:var(--csm-ink);color:#fff;border-radius:var(--csm-radius);box-shadow:0 8px 28px rgba(18,20,23,.28);font:14px/1.35 var(--csm-f-data);box-sizing:border-box}
	.csm-ui.upd[hidden]{display:none}
	.upd-ic{flex:none;display:grid;place-items:center;width:32px;height:32px;border-radius:50%;background:var(--csm-live);font:700 18px/1 var(--csm-f-label)}
	.upd-tx{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
	.upd-tx b{font:700 13px/1.2 var(--csm-f-label);letter-spacing:.04em;text-transform:uppercase}
	.upd-tx span{color:#d5d8dd;font-size:13px}
	.upd-go{all:initial;flex:1 0 100%;box-sizing:border-box;min-height:44px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;padding:0 16px;border-radius:8px;background:#fff;color:var(--csm-ink);font:700 13px/1 var(--csm-f-label);letter-spacing:.03em;text-transform:uppercase}
	.upd-go:hover{background:#e9ebee}
	.upd-go:focus-visible{outline:2px solid #fff;outline-offset:2px}
	@media (min-width:600px){
		.upd-go{flex:none}
		.csm-ui.upd{flex-wrap:nowrap;left:50%;right:auto;transform:translateX(-50%);width:min(560px,calc(100vw - 150px));bottom:max(24px,env(safe-area-inset-bottom))}
	}
	.csm-ui.upd-veil{position:fixed;inset:0;z-index:2147483000;display:grid;place-items:center;padding:16px;background:rgba(255,255,255,.82);backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);box-sizing:border-box}
	.csm-ui.upd-veil[hidden]{display:none}
	.upd-card{display:flex;flex-direction:column;align-items:center;gap:8px;max-width:320px;padding:24px 28px;background:#fff;border:1px solid var(--csm-line);border-radius:var(--csm-radius);box-shadow:0 12px 40px rgba(18,20,23,.16);text-align:center;font:14px/1.4 var(--csm-f-data);color:var(--csm-text)}
	.upd-card:focus{outline:none}
	.upd-card b{font:700 20px/1.2 var(--csm-f-display);color:var(--csm-ink);text-transform:uppercase;letter-spacing:.02em}
	.upd-spin{width:34px;height:34px;margin-bottom:6px;border-radius:50%;border:3px solid var(--csm-line);border-top-color:var(--csm-live);animation:upd-spin .9s linear infinite}
	@keyframes upd-spin{to{transform:rotate(360deg)}}
	@media (prefers-reduced-motion:reduce){.upd-spin{animation:none;border-top-color:var(--csm-line-strong)}}
	@media print{.csm-ui.upd,.csm-ui.upd-veil,.csm-ui.upd-ver{display:none}}
	/* ---- The site simplified around the riders' needs (frozen mockup design/simplify-mockup/mockup-v7.html, 2.28.0): the
	   header's season, favourites and rider search, the season tile, one banner, the championship's calendar, the title
	   race's strip, the rider's next race, « Meneurs par catégorie » aligned ---- */

	/* P2: the one rider search, in the header (the results drop down over the page) */
	.csm-widget .top .gslot{position:relative;flex:1 1 300px;max-width:480px;margin-left:auto}
	.csm-widget .usearch .srow{display:flex;align-items:center;gap:8px}
	.csm-widget .usearch .search{flex:1;min-width:0;margin:0;max-width:none}
	@media (max-width:599px){
		.csm-widget .top .gslot{flex-basis:100%;max-width:none}
	}
	/* Favourites: « ★ Favoris 2 » beside the search, and its panel */
	.csm-widget .favwrap{position:relative;flex:0 0 auto}
	.csm-widget .favbtn{display:inline-flex;align-items:center;gap:6px;height:38px;box-sizing:border-box;padding:0 12px;border:1px solid var(--csm-line-strong);border-radius:999px;background:var(--csm-bg);color:var(--csm-ink);font:700 13px/1 var(--csm-f-label);cursor:pointer;white-space:nowrap}
	.csm-widget .favbtn .st{font-size:15px;color:var(--csm-muted)}
	.csm-widget .favbtn.on{border-color:#e3cf8f;background:var(--csm-gold-bg)}
	.csm-widget .favbtn.on .st{color:var(--csm-gold)}
	.csm-widget .favbtn .n{min-width:18px;height:18px;padding:0 5px;box-sizing:border-box;border-radius:999px;background:var(--csm-ink);color:#fff;font:800 11px/18px var(--csm-f-data);text-align:center}
	.csm-widget .favbtn:hover{border-color:var(--csm-gold)}
	.csm-widget .favbtn:focus-visible{outline:2px solid var(--csm-ink);outline-offset:2px}
	.csm-widget .favpanel[hidden]{display:none}
	.csm-widget .favpanel{position:absolute;right:0;top:calc(100% + 6px);z-index:31;width:min(360px,calc(100vw - 32px));padding:12px 12px 10px;background:var(--csm-bg);border:1px solid var(--csm-line);border-radius:var(--csm-radius);box-shadow:0 12px 32px rgba(18,20,23,.16)}
	.csm-widget .favpanel .fh{margin:0 0 8px;font:800 10px var(--csm-f-label);letter-spacing:.12em;text-transform:uppercase;color:var(--csm-muted)}
	.csm-widget .favlist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
	.csm-widget .favlist li{display:flex;align-items:stretch;border:1px solid var(--csm-line);border-radius:8px;overflow:hidden}
	.csm-widget .favlist .fgo{flex:1;min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:3px;padding:8px 10px;border:0;background:none;text-align:left;cursor:pointer}
	.csm-widget .favlist .fgo b{font:800 italic 15px/1.1 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink)}
	.csm-widget .favlist .fgo span{font:600 11px var(--csm-f-label);color:var(--csm-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
	.csm-widget .favlist .fgo:hover{background:var(--csm-panel)}
	.csm-widget .favlist .frm{flex:0 0 40px;border:0;border-left:1px solid var(--csm-line);background:none;font-size:18px;color:var(--csm-muted);cursor:pointer}
	.csm-widget .favlist .frm:hover{background:var(--csm-panel);color:var(--csm-dnf)}
	.csm-widget .favlist button:focus-visible{outline:2px solid var(--csm-ink);outline-offset:-2px}
	.csm-widget .favempty{margin:0;font:600 13px/1.45 var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .favpanel .fnote{margin:8px 0 0;font:600 11px var(--csm-f-label);color:var(--csm-muted)}
	@media (max-width:599px){
		.csm-widget .favbtn .lb{display:none}
		.csm-widget .favbtn{padding:0 10px}
		.csm-widget .favpanel{left:0;right:auto}
	}

	/* The season after the title: « CLASSEMENTS 2026 ⌄ » */
	.csm-widget .ttl{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap}
	.csm-widget .vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
	.csm-widget .ypick{position:relative;display:inline-flex}
	/* a touch screen: the browser's own picker, drawn like the title */
	.csm-widget .ysel{appearance:none;-webkit-appearance:none;border:0;border-radius:8px;margin:0;padding:0 34px 0 4px;font:800 italic clamp(28px,5vw,40px)/1.05 var(--csm-f-display);color:var(--csm-gold);background:transparent url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='10'%3E%3Cpath d='M1.5 1.5l6.5 6.5 6.5-6.5' fill='none' stroke='%23B8952E' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer}
	.csm-widget .ysel:focus-visible{outline:2px solid var(--csm-ink);outline-offset:3px}
	.csm-widget .ybtn{display:none}
	/* a mouse: our button and menu */
	@media (hover:hover) and (pointer:fine){
		.csm-widget .ysel{display:none}
		.csm-widget .ybtn{display:inline-flex;align-items:center;gap:10px;margin:0;padding:2px 10px 2px 6px;border:1px solid transparent;border-radius:10px;background:none;font:800 italic clamp(28px,5vw,40px)/1.05 var(--csm-f-display);color:var(--csm-gold);cursor:pointer;transition:background .15s,border-color .15s}
		.csm-widget .ybtn svg{color:var(--csm-gold);transition:transform .15s}
		.csm-widget .ybtn:hover{background:var(--csm-gold-bg);border-color:#ecdcae}
		.csm-widget .ybtn[aria-expanded="true"]{background:var(--csm-gold-bg);border-color:#e3cf8f}
		.csm-widget .ybtn[aria-expanded="true"] svg{transform:rotate(180deg)}
		.csm-widget .ybtn:focus-visible{outline:2px solid var(--csm-ink);outline-offset:2px}
	}
	.csm-widget .ymenu{position:absolute;left:0;top:calc(100% + 8px);z-index:32;min-width:190px;padding:8px;background:var(--csm-bg);border:1px solid var(--csm-line);border-radius:12px;box-shadow:0 14px 36px rgba(18,20,23,.18)}
	.csm-widget .ymenu[hidden]{display:none}
	.csm-widget .ymenu .yh{margin:2px 8px 6px;font:800 10px var(--csm-f-label);letter-spacing:.12em;text-transform:uppercase;color:var(--csm-muted)}
	.csm-widget .yopt{display:flex;align-items:center;justify-content:space-between;width:100%;padding:8px 10px;border:0;border-radius:8px;background:none;cursor:pointer}
	.csm-widget .yopt b{font:800 italic 24px/1 var(--csm-f-display);color:var(--csm-ink)}
	.csm-widget .yopt .ok{font:800 14px var(--csm-f-label);color:var(--csm-gold)}
	.csm-widget .yopt[aria-selected="true"]{background:var(--csm-gold-bg)}
	.csm-widget .yopt:hover{background:var(--csm-panel)}
	.csm-widget .yopt:focus-visible{outline:2px solid var(--csm-ink);outline-offset:-2px}

	/* The season tile: an icon and « Vue d'ensemble » (the year is in the title) */
	.csm-widget .tile.overview .ovi{fill:var(--csm-ink)}
	.csm-widget .tile.overview[aria-pressed="false"] .ovi{fill:var(--csm-muted)}

	/* P6a: the banner's next race opens its event */
	.csm-widget .nextrow.go{cursor:pointer;border-radius:6px}
	.csm-widget .nextrow.go:hover b{text-decoration:underline;text-underline-offset:3px}
	.csm-widget .nextrow.go:focus-visible{outline:2px solid var(--csm-ink);outline-offset:3px}
	.csm-widget .nextrow .chev{margin-left:auto;font:800 18px/1 var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .banner .grow{min-width:0}


	/* P5: the title race's strip opens the deciding race's event */
	.csm-widget .tstrip.go{cursor:pointer;transition:box-shadow .15s}
	.csm-widget .tstrip.go:hover{box-shadow:0 8px 22px rgba(18,20,23,.22)}
	.csm-widget .tstrip.go:focus-visible{outline:3px solid var(--csm-gold);outline-offset:2px}
	.csm-widget .tstrip .tsub b{color:#fff;font-weight:800}
	.csm-widget .tstrip .tlive{color:#5fd78f;font-weight:800}
	.csm-widget .tstrip .tgo{font:700 12px var(--csm-f-label);color:#fff;white-space:nowrap;border:1px solid #4a4f57;border-radius:999px;padding:6px 12px}
	.csm-widget .tstrip.go:hover .tgo{background:#fff;color:var(--csm-ink)}

	/* P6b: a championship's header on a rider's page opens it */
	.csm-widget .cg-h{position:relative}
	.csm-widget .cg-h:hover .t{text-decoration:underline;text-underline-offset:3px}
	.csm-widget .cg-h .lgo:focus-visible{outline:2px solid var(--csm-ink);outline-offset:3px;border-radius:6px}

	/* A rider's next race, under their name */
	.csm-widget .pnext{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 8px;margin-top:10px;padding:7px 12px;border:1px solid var(--csm-line);border-radius:8px;background:var(--csm-bg);text-align:left;cursor:pointer;font:600 12px var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .pnext .k{font:800 10px var(--csm-f-label);letter-spacing:.12em;text-transform:uppercase}
	.csm-widget .pnext b{font:800 italic 15px var(--csm-f-display);color:var(--csm-ink);text-transform:uppercase}
	.csm-widget .pnext .chev{font:800 16px/1 var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .pnext:hover b{text-decoration:underline;text-underline-offset:3px}
	.csm-widget .pnext:focus-visible{outline:2px solid var(--csm-ink);outline-offset:2px}

	/* Many favourites: the list scrolls inside the panel, which never runs past the screen */
	.csm-widget .favlist{max-height:min(52vh,380px);overflow-y:auto;overscroll-behavior:contain;padding-right:2px}
	.csm-widget .favlist li{flex:0 0 auto}

	/* The championship's banner without its logo (the chosen tile shows it just above) */
	.csm-widget .banner.nologo{padding:14px 18px}
	.csm-widget .banner.nologo .t{margin-bottom:4px}

	/* The championship's calendar under the banner: the dashboard's */
	.csm-widget .banner+.section-h{margin-top:22px}
	.csm-widget .cal+.mode-body{margin-top:16px}
	.csm-widget .cal.notags{grid-template-rows:repeat(3,auto)}
	/* One banner (the season, a championship): the title and status on the left, the figures on the right (under the title
	   on a phone), a row at the bottom (a championship's next race) */
	.csm-widget .banner.ub{display:block;margin-bottom:18px}
	.csm-widget .ub-top{display:flex;flex-wrap:wrap;align-items:center;gap:12px 24px}
	.csm-widget .ub-top .grow{min-width:0;flex:1 1 280px}
	.csm-widget .banner.ub .stats{border:0;background:none;padding:0;gap:8px 32px}
	.csm-widget .ub-figs{font:600 13px var(--csm-f-label);color:var(--csm-text)}
	@media (max-width:599px){
		.csm-widget .banner.ub .stats{flex-basis:100%;gap:8px 22px}
	}
	/* « Meneurs par catégorie »: the class, the name, the points (right), the gap (left), the ✓: the same columns on every row */
	.csm-widget .lrow,.csm-widget .lpanel.open .lrow{grid-template-columns:92px minmax(0,1fr) 64px 34px 16px}
	.csm-widget .lrow .lpts{text-align:right;white-space:nowrap}
	.csm-widget .lrow .gap{margin-left:0;white-space:nowrap}
	@media (max-width:420px){
		.csm-widget .lrow,.csm-widget .lpanel.open .lrow{grid-template-columns:76px minmax(0,1fr) 58px 30px 16px;gap:8px;padding:8px 12px}
		.csm-widget .lrow .nm{white-space:normal;overflow:visible;line-height:1.2}
	}
	/* The places (3.0.0): the map's pin beside the place's name; the place on an event's page; the Vérification page's list */
	.csm-widget .sched.map{padding:1px 2px;margin-left:3px;vertical-align:-3px}
	.csm-widget .cal-item .ve .sched.map{align-self:auto;margin-left:3px}
	.csm-widget .eplace{display:inline-flex;align-items:center;gap:10px;margin-top:10px;padding:8px 12px;border:1px solid var(--csm-line);border-radius:8px;color:var(--csm-ink);text-decoration:none;max-width:100%;box-sizing:border-box}
	.csm-widget .eplace svg{flex:0 0 auto;width:18px;height:18px;color:var(--csm-muted)}
	.csm-widget .eplace span{display:flex;flex-direction:column;min-width:0}
	.csm-widget .eplace b{font:700 14px var(--csm-f-data)}
	.csm-widget .eplace small{font:600 12px var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .eplace .go{flex:0 0 auto;font:700 12px var(--csm-f-label);color:var(--csm-ink);margin-left:6px}
	.csm-widget .eplace:hover{border-color:var(--csm-line-strong)}
	.csm-widget .eplace:hover b{text-decoration:underline;text-underline-offset:3px}
	.csm-widget .eplace:focus-visible{outline:2px solid var(--csm-ink);outline-offset:2px}
	.csm-widget div.eplace{cursor:default}
	.csm-check .ck-places td a{font-weight:700;color:var(--csm-ink)}
	.csm-check .ck-places td em{color:var(--csm-muted)}

	/* The rider search (frozen mockup design/search-mockup/mockup-v1.html, 4.1.0): one line per rider (the plate, the name, a ›),
	   a combobox; on a phone a full-screen panel on the part of the screen the keyboard leaves (--svt, --svh) */
	.csm-widget .usearch .search input{padding:9px 36px 9px 12px;font-size:14px}
	/* 16 px on a touch screen or a phone: a smaller text makes iOS zoom in on the field */
	@media (pointer:coarse),(max-width:599px){
		.csm-widget .usearch .search input{font-size:16px}
	}
	.csm-widget .usearch .dres{position:absolute;left:0;right:0;top:calc(100% + 6px);z-index:30;box-sizing:border-box;overflow-y:auto;overscroll-behavior:contain;padding:8px;background:var(--csm-bg);border:1px solid var(--csm-line);border-radius:12px;box-shadow:0 18px 48px rgba(18,20,23,.18),0 2px 6px rgba(18,20,23,.06)}
	.csm-widget .usearch .dres[hidden]{display:none}
	/* « PILOTES 41 » over the list */
	.csm-widget .shead{display:flex;align-items:center;gap:8px;margin:0;padding:6px 10px 8px;font:800 10px var(--csm-f-label);letter-spacing:.12em;text-transform:uppercase;color:var(--csm-muted)}
	.csm-widget .shead .n{min-width:20px;height:18px;padding:0 6px;box-sizing:border-box;border-radius:999px;background:var(--csm-panel);color:var(--csm-text);font:800 11px/18px var(--csm-f-data);letter-spacing:0;text-align:center}
	.csm-widget .slist{list-style:none;margin:0;padding:0}
	.csm-widget .sopt{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:14px;min-height:50px;box-sizing:border-box;padding:8px 10px;border-radius:8px;cursor:pointer}
	/* a thin line between the rows, starting after the plate; none around the row hovered or chosen */
	.csm-widget .sopt+.sopt{background-image:linear-gradient(var(--csm-line),var(--csm-line));background-size:calc(100% - 78px) 1px;background-position:right 10px top;background-repeat:no-repeat}
	.csm-widget .sopt:hover,.csm-widget .sopt[aria-selected="true"]{background:var(--csm-panel)}
	.csm-widget .sopt:hover+.sopt,.csm-widget .sopt[aria-selected="true"]+.sopt{background-image:none}
	.csm-widget .sopt[aria-selected="true"]{box-shadow:inset 3px 0 0 var(--csm-gold)}
	/* the plate: solid black, the number white, like a race plate */
	.csm-widget .splates{display:flex;gap:4px;min-width:44px}
	.csm-widget .splate{display:inline-flex;align-items:center;justify-content:center;min-width:44px;height:28px;box-sizing:border-box;padding:0 6px;border-radius:5px;background:var(--csm-ink);font:800 italic 16px/1 var(--csm-f-display);font-variant-numeric:tabular-nums;color:#fff}
	.csm-widget .sname{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font:800 italic 15px/1.2 var(--csm-f-display);text-transform:uppercase;letter-spacing:.01em;color:var(--csm-ink)}
	/* the part found: a gold underline (a tint behind italic capitals cuts into them) */
	.csm-widget .sname mark{background:none;color:inherit;text-decoration:underline;text-decoration-color:var(--csm-gold);text-decoration-thickness:2px;text-underline-offset:3px}
	.csm-widget .sfav{margin-right:6px;font-style:normal;color:var(--csm-gold)}
	/* the › that says the row opens the rider's page */
	.csm-widget .sgo{font:700 20px/1 var(--csm-f-label);color:var(--csm-line-strong);transition:color .15s,transform .15s}
	.csm-widget .sopt:hover .sgo,.csm-widget .sopt[aria-selected="true"] .sgo{color:var(--csm-gold);transform:translateX(2px)}
	@media (prefers-reduced-motion:reduce){.csm-widget .sgo{transition:none}}
	.csm-widget .shint,.csm-widget .sempty{margin:0;padding:14px 10px;font:600 13px var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .smore{margin:6px 0 0;padding:10px 10px 4px;border-top:1px solid var(--csm-line);font:600 12px var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .usearch .dres .shint{display:none}
	.csm-widget .scancel{display:none}
	@media (max-width:599px){
		.csm-widget .sopt{min-height:54px}
	}
	/* the phone panel */
	.csm-widget.sopen .usearch{position:fixed;left:0;right:0;top:var(--svt,0);height:var(--svh,100dvh);z-index:1000;display:flex;flex-direction:column;background:var(--csm-bg)}
	.csm-widget.sopen .usearch .srow{padding:10px 12px;border-bottom:1px solid var(--csm-line);background:var(--csm-bg)}
	.csm-widget.sopen .favwrap{display:none}
	.csm-widget.sopen .scancel{display:block;flex:0 0 auto;border:0;background:none;padding:8px 2px 8px 6px;font:700 15px var(--csm-f-label);color:var(--csm-ink);cursor:pointer}
	.csm-widget.sopen .usearch .dres{position:static;flex:1 1 auto;width:auto;max-height:none;padding:6px 8px 16px;border:0;border-radius:0;box-shadow:none}
	.csm-widget.sopen .usearch .dres .shint{display:block}
	.csm-widget .scancel:focus-visible{outline:2px solid var(--csm-ink);outline-offset:2px}
	/* The highlights (5.0.0, frozen mockup design/highlights-mockup/mockup-v3.html): « ? Détails », the Défi (no organizers' mark on the public pages since 5.4.1), the Gala section. */
	/* The Palmarès's cards: « ? Détails » at the bottom of each, aligned from card to card. */
	/* « ? » at the top right of each card (user, 2026-10-04), 28 px, 44 px to touch; the first lines keep clear of it. */
	.csm-widget .gl-fact{position:relative;display:flex;flex-direction:column;align-items:flex-start}
	.csm-widget .gl-fact .gl-fv{align-self:stretch}
	.csm-widget .gl-fact>.lab,.csm-widget .gl-fact>.gl-defi{max-width:calc(100% - 36px)}
	.csm-widget .gl-more{position:absolute;top:10px;right:10px;display:flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:1px solid var(--csm-line-strong);border-radius:50%;background:#fff;font:800 13px var(--csm-f-label);color:var(--csm-ink);cursor:pointer}
	.csm-widget .gl-more::before{content:"";position:absolute;inset:-8px}
	.csm-widget .gl-more:hover{border-color:var(--csm-ink);background:var(--csm-ink);color:#fff}
	/* A Défi: a dashed frame, the goal instead of a figure. */
	.csm-widget .gl-fact.defi{border-style:dashed;border-color:var(--csm-line-strong);background:linear-gradient(180deg,#f3f4f6,#fff 70%)}
	.csm-widget .gl-defi{display:inline-flex;margin:0 0 6px;padding:2px 9px;border-radius:999px;border:1px solid var(--csm-ink);color:var(--csm-ink);font:800 10px/1.6 var(--csm-f-label);letter-spacing:.06em;text-transform:uppercase}
	.csm-widget .gl-fv.goal{flex-wrap:wrap}
	.csm-widget .gl-fv.goal b{color:var(--csm-muted);white-space:nowrap}
	.csm-widget .gl-fv .obj{align-self:center;font:800 10px var(--csm-f-label);letter-spacing:.08em;text-transform:uppercase}
	.csm-widget .gl-fact.defi p{color:var(--csm-text);font-size:13px}
	/* The explanation's box (the legend's <dialog>). */
	.csm-widget dialog.hexplain .h-b p{margin:0 0 10px;font:500 14px/1.55 var(--csm-f-label);color:var(--csm-text)}
	.csm-widget dialog.hexplain .hx-kind{font:800 11px var(--csm-f-label) !important;letter-spacing:.08em;text-transform:uppercase;color:var(--csm-gold) !important}
	.csm-widget dialog.hexplain .hx-defi{padding:10px 12px;border:1px dashed var(--csm-line-strong);border-radius:10px;background:#f3f4f6}
	.csm-widget dialog.hexplain .hx-defi b{display:block;font:800 11px var(--csm-f-label);letter-spacing:.08em;text-transform:uppercase;color:var(--csm-ink)}
	.csm-widget dialog.hexplain .jlink{color:var(--csm-ink);font-weight:700}
	/* The show: a Défi's slide, the tools' « i ». */
	.csm-widget .sl-defi .sl-kicker{padding:6px 18px;border:2px solid #f3d77a;border-radius:999px}
	.csm-widget .sl-big.goal{align-items:center}
	.csm-widget .sl-big.goal b{white-space:nowrap}
	.csm-widget .sl-big.goal b{color:#c9ccd2;text-shadow:none;font-size:200px}
	.csm-widget .sl-big .obj{font:800 28px var(--csm-f-label);letter-spacing:.22em;color:#9ba1ab}
	.csm-widget .sl-defit{max-width:1100px;font-size:34px;line-height:1.35;color:#e8e9ec}
	.csm-widget .gl-tools .gl-info{font:italic 800 20px Georgia,serif}
	/* The Vérification page's « Gala » section. */
	.csm-widget .ck-gala .hg-champs{table-layout:fixed;width:100%}
	.csm-widget .ck-gala .hg-champs th:first-child{width:130px}
	.csm-widget .ck-gala .hg-champs td{white-space:normal}
	.csm-widget .ck-gala .hg-k{display:inline-block;margin:0 14px 4px 0;white-space:nowrap}
	.csm-widget .ck-gala .hg-k.blocked{color:var(--csm-dnf)}
	.csm-widget .ck-gala .hg-st{display:inline-block;padding:1px 8px;border-radius:999px;font:800 10px/1.7 var(--csm-f-label);letter-spacing:.06em;text-transform:uppercase;white-space:nowrap}
	.csm-widget .ck-gala .hg-st.on{background:#e6f2e8;color:#1f6b33}
	.csm-widget .ck-gala .hg-st.pick{background:#b3261e;color:#fff}
	.csm-widget .ck-gala .hg-st.defi{border:1px dashed var(--csm-ink);color:var(--csm-ink)}
	.csm-widget .ck-gala .hg-st.hidden{background:var(--csm-panel);color:var(--csm-muted)}
	.csm-widget .ck-gala tr.off th,.csm-widget .ck-gala tr.off td{color:var(--csm-muted)}
	.csm-widget .ck-gala .hg-n{width:24px;font-weight:700}
	.csm-widget .ck-gala .hg-facts td:last-child{white-space:normal;min-width:240px}
	/* A highlight with a trophy (TROPHY = YES, 5.6.0; frozen mockup design/trophy-mockup/mockup-v1.html: card A « Écrin
	   doré », slide A « Au centre »): the cup drawn, the same on every device. */
	.csm-widget .tr-svg{display:block;width:100%;height:100%;overflow:visible}
	/* The Palmarès's card: the gold frame, the cup and « Trophée » above the title, the names as a champion's. */
	.csm-widget .gl-fact.trophy:not(.to-win){border:2px solid var(--csm-gold);background:linear-gradient(180deg,#f5e3a5,#fbf5e4 40%,#fff 85%);box-shadow:0 8px 22px rgba(184,149,46,.22)}
	.csm-widget .gl-fact.trophy .tr-head{display:flex;align-items:center;gap:8px;max-width:calc(100% - 36px);margin:0 0 8px}
	.csm-widget .gl-fact.trophy .tr-head .tr-svg{flex:none;width:36px;height:40px;filter:drop-shadow(0 2px 4px rgba(122,95,23,.35))}
	.csm-widget .gl-fact.trophy.to-win .tr-svg{filter:none;opacity:.8}
	.csm-widget .tr-pill{padding:2px 10px;border-radius:999px;background:linear-gradient(180deg,#f3d77a,#B8952E);color:#121417;font:800 10px/1.7 var(--csm-f-label);letter-spacing:.08em;text-transform:uppercase;white-space:nowrap}
	.csm-widget .tr-pill.muted{background:none;border:1px dashed var(--csm-line-strong);color:var(--csm-muted)}
	.csm-widget .gl-fact.trophy .gl-fw .who,.csm-widget .gl-fact.trophy .gl-fw span{font:800 italic 20px/1.1 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink);padding:0}
	/* The show: the 1st click the title and the cup in its light, the 2nd the figure and the names in gold, the confetti. */
	.csm-widget .sl-fact.trophy .tr-stage{position:relative;display:grid;place-items:center}
	.csm-widget .sl-fact.trophy .tr-stage .tr-svg{position:relative;width:280px;height:315px;filter:drop-shadow(0 0 40px rgba(243,215,122,.5));animation:sl-pop .8s cubic-bezier(.2,.8,.2,1) both}
	.csm-widget .tr-rays{position:absolute;left:50%;top:50%;width:760px;height:760px;margin:-380px 0 0 -380px;border-radius:50%;background:repeating-conic-gradient(from 0deg,transparent 0 12deg,rgba(243,215,122,.16) 18deg,transparent 24deg 42deg,rgba(243,215,122,.12) 48deg,transparent 54deg 60deg);-webkit-mask:radial-gradient(closest-side,#000 25%,transparent);mask:radial-gradient(closest-side,#000 25%,transparent);animation:sl-beams 30s linear infinite;pointer-events:none}
	.csm-widget .tr-crown{padding:6px 16px;border-radius:999px;background:#f3d77a;color:#121417;font:800 16px var(--csm-f-label);letter-spacing:.14em;text-transform:uppercase;box-shadow:0 0 30px rgba(243,215,122,.55)}
	.csm-widget .tr-crown.muted{background:none;border:2px dashed #9ba1ab;color:#c9ccd2;box-shadow:none}
	.csm-widget .sl-fact.trophy.hold :is(.sl-big,.sl-names,.sl-list,.sl-duels,.sl-detail){display:none}
	.csm-widget .sl-fact.trophy.won .sl-names span,.csm-widget .sl-fact.trophy.won .sl-list b,.csm-widget .sl-fact.trophy.won .sl-duels .win b{padding:0 .2em;background:linear-gradient(180deg,#fff 30%,#f3d77a);-webkit-background-clip:text;background-clip:text;color:transparent}
	.csm-widget .sl-fact.trophy.won .sl-names span{font-size:64px}
	.csm-widget .sl-fact.trophy.won .tr-stage .tr-svg{width:130px;height:146px}
	.csm-widget .sl-fact.trophy.won.has-list .tr-stage .tr-svg{width:70px;height:79px}
	.csm-widget .sl-fact.trophy.won .tr-rays{width:420px;height:420px;margin:-210px 0 0 -210px}
	.csm-widget .sl-fact.trophy.won.has-list .tr-crown{display:none}
	.csm-widget .sl-fact.trophy.won:not(.whole) .tr-stage .tr-svg{animation:tr-shrink .9s cubic-bezier(.2,.8,.2,1) both,sl-glow 1.2s ease-out both}
	.csm-widget .sl-fact.trophy.won.has-list:not(.whole) .tr-stage .tr-svg{animation:tr-shrink-list .9s cubic-bezier(.2,.8,.2,1) both,sl-glow 1.2s ease-out both}
	/* « Auto »: the cup shrinks, the figure and the names come by themselves at --d. */
	.csm-widget .sl-fact.trophy.auto :is(.sl-big,.sl-names,.sl-list,.sl-duels,.sl-detail){animation:sl-rise .8s var(--d) cubic-bezier(.2,.8,.2,1) both}
	.csm-widget .sl-fact.trophy.auto .tr-stage .tr-svg{animation:sl-pop .8s cubic-bezier(.2,.8,.2,1) both,tr-shrink .9s var(--d) cubic-bezier(.2,.8,.2,1) both,sl-glow 1.2s var(--d) ease-out both}
	.csm-widget .sl-fact.trophy.auto.has-list .tr-stage .tr-svg{animation:sl-pop .8s cubic-bezier(.2,.8,.2,1) both,tr-shrink-list .9s var(--d) cubic-bezier(.2,.8,.2,1) both,sl-glow 1.2s var(--d) ease-out both}
	/* A Défi: the grey cup, « Trophée à gagner », no light. */
	.csm-widget .sl-fact.trophy.to-win .tr-stage .tr-svg{width:120px;height:135px;filter:none;opacity:.85}
	.csm-widget .sl-fact.trophy.to-win .sl-big.goal b{font-size:150px}
	/* The highlights' opener: a small cup before a highlight with a trophy. */
	.csm-widget .sl-teasers .tr-mini{display:inline-block;width:20px;height:23px;margin-right:8px;vertical-align:-4px}
	/* The Vérification page's Gala tab: « Trophée » beside the state. */
	.csm-widget .ck-gala .hg-st.tr{display:inline-flex;align-items:center;gap:4px;margin-left:4px;background:linear-gradient(180deg,#f3d77a,#B8952E);color:#121417}
	.csm-widget .ck-gala .hg-st.tr .tr-svg{width:11px;height:12px}
	.csm-widget .ck-gala .hg-st.tr.muted{background:none;border:1px dashed var(--csm-line-strong);color:var(--csm-muted)}
	@keyframes tr-shrink{from{width:280px;height:315px}to{width:130px;height:146px}}
	@keyframes tr-shrink-list{from{width:280px;height:315px}to{width:70px;height:79px}}
	@media (prefers-reduced-motion:reduce){
		.csm-widget .tr-rays{animation:none}
		.csm-widget .sl-fact.trophy .tr-stage .tr-svg,.csm-widget .sl-fact.trophy.auto :is(.sl-big,.sl-names,.sl-list,.sl-duels,.sl-detail){animation:none}
	}
	/* The gifts (5.9.0; frozen mockup design/gift-mockup/mockup-v2.html, variant C « Sous le projecteur »): no text, a
	   champagne box with a black ribbon on a plinth, in a beam of light, gold dust drifting up in it. */
	.csm-widget .sl-gift{padding:0;display:block}
	.csm-widget .gf-art{position:absolute;left:50%;top:50%;width:900px;height:900px;margin:-470px 0 0 -450px;overflow:visible}
	.csm-widget .gf-beam{opacity:0;animation:gf-fade 1.8s ease-out .1s forwards}
	.csm-widget .gf-plinth{opacity:0;animation:gf-fade 1s ease-out .2s forwards}
	.csm-widget .gf-box{animation:sl-rise 1.4s cubic-bezier(.2,.7,.2,1) .5s both}
	.csm-widget .gf-sheen{transform:translateX(-520px) skewX(-20deg);animation:gf-sweep 2.4s cubic-bezier(.45,0,.2,1) 2s both,gf-sweep 2.4s cubic-bezier(.45,0,.2,1) 10s infinite}
	.csm-widget .gf-mote{position:absolute;width:4px;height:4px;border-radius:50%;background:#f3d77a;box-shadow:0 0 8px rgba(243,215,122,.8);opacity:0;animation:gf-mote 9s ease-in-out infinite}
	@keyframes gf-fade{to{opacity:1}}
	@keyframes gf-sweep{from{transform:translateX(-520px) skewX(-20deg)}to{transform:translateX(620px) skewX(-20deg)}}
	@keyframes gf-mote{0%{opacity:0;transform:translate(0,0)}25%{opacity:.55}75%{opacity:.35}100%{opacity:0;transform:translate(30px,-140px)}}
	@media (prefers-reduced-motion:reduce){
		.csm-widget .gf-beam,.csm-widget .gf-plinth{opacity:1}
		.csm-widget .gf-sheen,.csm-widget .gf-mote{display:none}
	}
	/* The promotions (5.10.0; frozen mockups design/promotions-mockup/mockup-v1.html, variant B « Licence », and
	   mockup-v2.html for several plates): the opener's disciplines, then a licence per rider. A licence waits (.hold)
	   with the passage only; .won strikes the old level, stamps the new one, brings the plate(s) and the name in, after
	   --d (« Auto »); .whole is the final picture at once. Each animation starts from its hidden state, so without it
	   (.whole, less motion) the final state shows. */
	.csm-widget .pr-discs{display:flex;gap:28px;margin-top:22px}
	.csm-widget .pr-disc{min-width:380px;padding:22px 30px 24px;border:1.5px solid rgba(243,215,122,.45);border-radius:18px;background:rgba(243,215,122,.05);text-align:left;animation:sl-pop .6s cubic-bezier(.2,.8,.2,1) both;animation-delay:calc(.7s + var(--i) * .15s)}
	.csm-widget .pr-disc h3{margin:0 0 12px;font:800 italic 46px/1 var(--csm-f-display);text-transform:uppercase;color:#f3d77a;text-align:center}
	.csm-widget .pr-disc p{margin:6px 0 0;display:flex;justify-content:space-between;align-items:baseline;gap:24px;font:600 22px var(--csm-f-label);color:#c9ccd2}
	.csm-widget .pr-disc p b{font:800 italic 28px var(--csm-f-display);color:#fff}
	.csm-widget .sl-promo{align-items:center;justify-content:center;padding-bottom:90px;--d:0s}
	.csm-widget .pr-card{position:relative;width:1180px;height:600px;flex:none;border-radius:28px;overflow:hidden;background:linear-gradient(135deg,#1c1f24,#0e1013 60%,#16181c);box-shadow:0 40px 90px rgba(0,0,0,.7),inset 0 0 0 1px rgba(243,215,122,.25);animation:sl-rise 1s cubic-bezier(.2,.8,.2,1) both}
	.csm-widget .pr-card::before{content:"";position:absolute;inset:18px;border:1.5px solid rgba(243,215,122,.35);border-radius:18px;pointer-events:none}
	.csm-widget .pr-guil{position:absolute;right:-200px;bottom:-260px;width:640px;height:640px;opacity:.14;fill:none;stroke:#f3d77a;stroke-width:.5}
	.csm-widget .pr-sheen{position:absolute;top:0;bottom:0;width:240px;background:linear-gradient(90deg,transparent,rgba(255,255,255,.08),transparent);transform:translateX(-140%) skewX(-20deg)}
	.csm-widget .pr-head{position:absolute;left:64px;right:64px;top:44px;display:flex;justify-content:space-between;align-items:center;padding-bottom:20px;border-bottom:1px solid rgba(243,215,122,.25)}
	.csm-widget .pr-badge{min-width:120px;padding:8px 22px 10px;border-radius:12px;text-align:center;font:800 italic 40px/1 var(--csm-f-display);color:#121417;background:linear-gradient(180deg,#fff2c4,#f3d77a 45%,#B8952E);box-shadow:0 0 34px rgba(243,215,122,.35)}
	.csm-widget .pr-lic{font:700 20px var(--csm-f-label);letter-spacing:.18em;text-transform:uppercase;color:#80868f}
	.csm-widget .pr-body{position:absolute;left:64px;right:64px;top:150px;bottom:52px;display:grid;grid-template-columns:250px minmax(0,1fr);grid-template-rows:auto 1fr;gap:26px 46px}
	.csm-widget .pr-lab{display:block;margin-bottom:10px;font:800 15px var(--csm-f-label);letter-spacing:.22em;text-transform:uppercase;color:#80868f}
	.csm-widget .pr-pl{grid-row:1/3;display:flex;flex-direction:column}
	.csm-widget .pr-plates{display:flex;flex-direction:column;align-items:flex-start;gap:14px}
	.csm-widget .pr-plate{display:inline-grid;place-items:center;padding:0 18px;border-radius:12px;background:linear-gradient(180deg,#fdfbf4,#e7e2d2);color:#0b0c0f;font:800 italic 64px/1 var(--csm-f-display);box-shadow:0 0 0 3px #0b0c0f,0 0 0 5px rgba(243,215,122,.7),0 18px 40px rgba(0,0,0,.6)}
	.csm-widget .pr-plates.n1 .pr-plate{min-width:200px;height:150px;font-size:96px}
	.csm-widget .pr-plates.n2 .pr-plate{min-width:170px;height:108px;font-size:70px}
	.csm-widget .pr-plates.n3{gap:10px}
	.csm-widget .pr-plates.n3 .pr-plate{min-width:150px;height:84px;font-size:54px}
	.csm-widget .pr-plates.n4{display:grid;grid-template-columns:repeat(2,112px);gap:10px 12px}
	.csm-widget .pr-plates.n4 .pr-plate{width:112px;height:var(--h);padding:0 6px;border-radius:10px;font-size:calc(var(--h) * .66)}
	.csm-widget .pr-levels{display:flex;align-items:center;gap:34px}
	.csm-widget .pr-old{position:relative;padding:0 .1em;font:800 italic 64px/1 var(--csm-f-display);text-transform:uppercase;color:#c9ccd2}
	.csm-widget .pr-old::after{content:"";position:absolute;left:0;right:0;top:52%;height:5px;background:#d14b3f;transform:scaleX(0);transform-origin:left}
	.csm-widget .pr-arrow{font:700 46px var(--csm-f-label);color:#4d525c}
	.csm-widget .pr-stamp{padding:10px 30px 14px;border:4px solid rgba(243,215,122,.35);border-radius:12px;font:800 italic 76px/1 var(--csm-f-display);text-transform:uppercase;color:rgba(243,215,122,.35);transform:rotate(-4deg)}
	.csm-widget .pr-name{align-self:end;min-width:0;container-type:inline-size}
	.csm-widget .pr-blank{display:block;height:4px;width:100%;max-width:640px;margin-top:90px;background:repeating-linear-gradient(90deg,#3a3e46 0 18px,transparent 18px 28px)}
	.csm-widget .pr-in2{display:flex;flex-direction:column;gap:6px}
	.csm-widget .pr-first{font:700 italic 46px/1 var(--csm-f-display);text-transform:uppercase;color:#e8e9ec}
	/* The family name: its size from its length (--n letters), so the longest still fits the card (never cut). */
	.csm-widget .pr-last{align-self:flex-start;padding:0 .3em 0 .06em;margin-left:-.06em;white-space:nowrap;font:800 italic min(104px,calc(100cqi / (var(--n) * .62 + .4)))/.92 var(--csm-f-display);text-transform:uppercase;background:linear-gradient(180deg,#fff 25%,#f3d77a 65%,#B8952E);-webkit-background-clip:text;background-clip:text;color:transparent}
	.csm-widget .sl-promo.hold .pr-in{opacity:0}
	.csm-widget .sl-promo.won .pr-blank{display:none}
	.csm-widget .sl-promo.won .pr-old{color:#6b7079;animation:pr-dim .5s calc(var(--d) + .2s) both}
	.csm-widget .sl-promo.won .pr-old::after{transform:scaleX(1);animation:pr-strike .5s cubic-bezier(.6,0,.2,1) var(--d) both}
	.csm-widget .sl-promo.won .pr-stamp{color:#121417;background:linear-gradient(180deg,#fff2c4,#f3d77a 45%,#B8952E);border-color:#f3d77a;box-shadow:0 0 60px rgba(243,215,122,.5);animation:pr-stamp .55s cubic-bezier(.3,1.4,.5,1) calc(var(--d) + .45s) both}
	.csm-widget .sl-promo.won .pr-in1{animation:sl-rise .9s cubic-bezier(.2,.8,.2,1) calc(var(--d) + .9s) both}
	.csm-widget .sl-promo.won .pr-in2{animation:sl-rise .9s cubic-bezier(.2,.8,.2,1) calc(var(--d) + 1s) both}
	.csm-widget .sl-promo.won .pr-sheen{animation:pr-sweep 1.6s cubic-bezier(.45,0,.2,1) calc(var(--d) + 1.1s) both}
	.csm-widget .sl-promo.whole,.csm-widget .sl-promo.whole *,.csm-widget .sl-promo.whole ::after{animation:none}
	@keyframes pr-dim{from{color:#c9ccd2}}
	@keyframes pr-strike{from{transform:scaleX(0)}}
	@keyframes pr-stamp{from{transform:rotate(-4deg) scale(1.6);opacity:0}to{transform:rotate(-4deg) scale(1);opacity:1}}
	@keyframes pr-sweep{from{transform:translateX(-140%) skewX(-20deg)}to{transform:translateX(560%) skewX(-20deg)}}
	@media (prefers-reduced-motion:reduce){
		.csm-widget .pr-disc,.csm-widget .pr-card,.csm-widget .sl-promo *,.csm-widget .sl-promo ::after{animation:none}
	}
	/* The Palmarès's button in the season's banner (5.0.0). */
	.csm-widget .gl-entry.ub-gala{width:auto;flex:0 0 auto;margin:0;padding:12px 18px;gap:12px}
	.csm-widget .gl-entry.ub-gala b{font-size:20px}
	.csm-widget .gl-entry.ub-gala small{margin-top:2px;font-size:12px}
	.csm-widget .gl-entry.ub-gala .gl-entry-star{font-size:24px}
	.csm-widget .ub-quit .gl-quit{margin:12px 0 0}
	@media (max-width:599px){.csm-widget .gl-entry.ub-gala{width:100%}}
	/* The Vérification page's tabs (the bar's chips). */
	.csm-widget .ck-chip[aria-selected="true"]{background:var(--csm-ink);border-color:var(--csm-ink);color:#fff}
	.csm-widget .ck-chip[aria-selected="true"] span{color:inherit}
	.csm-widget .ck-part[hidden]{display:none}
	/* The explanation's example. */
	.csm-widget dialog.hexplain .h-b p.hx-ex{padding:8px 12px;border-left:3px solid var(--csm-gold);background:var(--csm-gold-bg);border-radius:0 8px 8px 0;font-size:13px}
	/* The media kit (MEDIA KIT; frozen mockup design/media-kit-mockup/mockup-v1.html, variant B): the card, the form. */
	.csm-widget .mk-card{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:12px 20px;margin-top:18px;padding:16px 18px;border:1px solid #e3cf8f;border-left:4px solid var(--csm-gold);border-radius:var(--csm-radius);background:var(--csm-gold-bg)}
	.csm-widget .mk-card p:not(.eyebrow){margin:0;font:500 14px/1.45 var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .mk-card .eyebrow{margin-bottom:4px;color:#7a5f14}
	.csm-widget .mk-main{display:inline-flex;align-items:center;gap:6px;min-height:42px;padding:0 18px;border:1px solid var(--csm-ink);border-radius:999px;background:var(--csm-ink);color:#fff;font:700 14px var(--csm-f-label);cursor:pointer}
	.csm-widget .mk-main:hover{background:#2a2e34}
	.csm-widget .mk-main[disabled]{background:var(--csm-line-strong);border-color:var(--csm-line-strong);color:var(--csm-bg);cursor:not-allowed}
	.csm-widget .mk-sec-b{min-height:42px;padding:0 16px;border:1px solid var(--csm-line-strong);border-radius:999px;background:var(--csm-bg);color:var(--csm-ink);font:700 14px var(--csm-f-label);cursor:pointer}
	.csm-widget .mk-sec-b:hover{border-color:var(--csm-ink)}
	.csm-widget .mk-small{min-height:34px;padding:0 12px;border:1px solid var(--csm-line-strong);border-radius:999px;background:var(--csm-bg);color:var(--csm-ink);font:700 12px var(--csm-f-label);cursor:pointer}
	.csm-widget .mk-small:hover{border-color:var(--csm-ink)}
	.csm-widget :is(.mk-main,.mk-sec-b,.mk-small,.mk-seg button):focus-visible{outline:2px solid var(--csm-ink);outline-offset:2px}
	.csm-widget .mk-lead{max-width:62ch;margin:0;font:500 14px/1.5 var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .mk-steps{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1px;margin:18px 0 0;padding:0;list-style:none;background:var(--csm-line);border:1px solid var(--csm-line);border-radius:var(--csm-radius);overflow:hidden}
	.csm-widget .mk-steps li{display:flex;align-items:center;gap:10px;padding:12px 14px;background:var(--csm-bg);font:700 13px/1.3 var(--csm-f-label);color:var(--csm-ink)}
	.csm-widget .mk-steps b{flex:0 0 auto;display:grid;place-items:center;width:26px;height:26px;border-radius:50%;background:var(--csm-ink);color:#fff;font:800 italic 14px var(--csm-f-display)}
	.csm-widget .mk-stored{margin:8px 0 0}
	.csm-widget .mk-sec{margin-top:8px}
	.csm-widget .mk-sec .section-h h2{display:flex;align-items:center;gap:10px}
	.csm-widget .mk-n{display:grid;place-items:center;width:24px;height:24px;border-radius:50%;border:1.5px solid var(--csm-ink);font:800 italic 13px var(--csm-f-display)}
	.csm-widget .mk-seg{display:inline-flex;border:1px solid var(--csm-line-strong);border-radius:999px;overflow:hidden;max-width:100%}
	.csm-widget .mk-seg button{min-height:42px;padding:0 18px;border:0;border-right:1px solid var(--csm-line-strong);background:var(--csm-bg);color:var(--csm-ink);font:700 14px var(--csm-f-label);cursor:pointer}
	.csm-widget .mk-seg button:last-child{border-right:0}
	.csm-widget .mk-seg button[aria-checked="true"]{background:var(--csm-ink);color:#fff}
	.csm-widget .mk-note{margin:10px 0 0;padding:10px 12px;border-left:3px solid var(--csm-gold);background:var(--csm-gold-bg);font:600 13px/1.45 var(--csm-f-label);color:#5f4a10;max-width:70ch}
	.csm-widget .mk-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px 18px}
	.csm-widget .mk-field{display:flex;flex-direction:column;gap:4px;margin-top:14px;min-width:0}
	.csm-widget .mk-grid .mk-field{margin-top:0}
	.csm-widget .mk-field>label{font:700 13px var(--csm-f-label);color:var(--csm-ink)}
	.csm-widget .mk-hint{margin:0;font:500 12px/1.45 var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .mk-in{position:relative;margin-top:4px}
	.csm-widget .mk-in :is(input,textarea){display:block;width:100%;box-sizing:border-box;padding:10px 12px;border:1px solid var(--csm-line-strong);border-radius:8px;background:var(--csm-bg);color:var(--csm-ink);font:500 14px/1.45 var(--csm-f-label);resize:vertical}
	.csm-widget .mk-in :is(input,textarea)::placeholder{color:#a3a8b0;font-style:italic}
	.csm-widget .mk-in :is(input,textarea):focus{outline:2px solid var(--csm-ink);outline-offset:0;border-color:var(--csm-ink)}
	.csm-widget .mk-in.over :is(input,textarea){border-color:var(--csm-dnf);outline-color:var(--csm-dnf);background:#fdf3f2}
	.csm-widget .mk-count{display:block;margin-top:3px;text-align:right;font:600 11px var(--csm-f-data);color:var(--csm-muted)}
	.csm-widget .mk-count.over{color:var(--csm-dnf);font-weight:800}
	.csm-widget .mk-langs.two{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:10px 18px}
	.csm-widget .mk-lang{display:inline-block;margin-top:4px;font:800 10px var(--csm-f-label);letter-spacing:.12em;text-transform:uppercase;color:var(--csm-muted)}
	.csm-widget .mk-photos{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:14px;margin-bottom:16px}
	.csm-widget .mk-photo{display:flex;flex-direction:column;gap:6px;align-items:flex-start}
	.csm-widget .mk-photo b{font:700 13px var(--csm-f-label);color:var(--csm-ink)}
	/* A partner's logo as the PDF's last page prints it (5.19.0, design/media-kit-logos-mockup/): the box .partner to scale
	   (1.735 × 0.9 in), the logo whole (at most 0.5 in high, 80 % wide), the name under it; no logo: the name alone, dashed. */
	.csm-widget .mk-logo{box-sizing:border-box;width:100%;max-width:280px;aspect-ratio:1.735/.9;container-type:inline-size;border:1px solid #e3e0da;border-radius:6px;background:#fff;overflow:hidden}
	.csm-widget .mk-logo.none{border:1px dashed var(--csm-line-strong)}
	.csm-widget .mk-logo-in{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2.4cqw;color:#6a6e75;font:6cqw/1.45 var(--csm-f-label);text-align:center}
	.csm-widget .mk-logo-in img{display:block;max-height:28.8cqw;max-width:80%;min-height:0;user-select:none;-webkit-user-drag:none}
	.csm-widget .mk-logo-in span{max-width:100%;overflow-wrap:anywhere}
	/* A photo card: its button at the bottom, so a row lines up whatever its hint (user, 2026-10-18). */
	.csm-widget .mk-photo .mk-hint{margin:0}
	.csm-widget .mk-photo>button{margin-top:auto}
	/* The photos in their PDF frame, the framing dragged (5.17.0, frozen mockup design/media-kit-focus-mockup/, variant A). */
	.csm-widget .mk-frames-lead{margin:0 0 14px;font:600 14px/1.5 var(--csm-f-label);color:var(--csm-ink);max-width:75ch}
	.csm-widget .mk-photos.mk-frames{display:flex;flex-wrap:wrap;gap:22px 18px;align-items:flex-start}
	.csm-widget .mk-frames .mk-photo{align-items:stretch;min-width:0}
	.csm-widget .mk-ph-cover{flex:0 1 340px}
	.csm-widget .mk-ph-portrait{flex:0 1 170px}
	.csm-widget :is(.mk-ph-profileLeft,.mk-ph-profileCenter,.mk-ph-profileRight){flex:0 1 220px}
	.csm-widget .mk-ph-offer{flex:1 1 100%;max-width:640px}
	.csm-widget .mk-ph-head{display:flex;align-items:center;gap:8px}
	.csm-widget .mk-sheet{width:17px;height:22px;flex:none;margin-left:auto}
	.csm-widget .mk-sheet .pg{fill:#fff;stroke:var(--csm-line-strong);stroke-width:.35}
	.csm-widget .mk-sheet .slot{fill:var(--csm-ink)}
	.csm-widget .mk-ph-page{font:600 12px var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .mk-frame{position:relative;width:100%;overflow:hidden;border-radius:6px;background:var(--csm-panel);border:1px solid var(--csm-line-strong);container-type:inline-size}
	.csm-widget .mk-frame img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;user-select:none;-webkit-user-drag:none}
	.csm-widget .mk-frame.none{border-style:dashed}
	.csm-widget .mk-frame.drag{cursor:grab;touch-action:none}
	.csm-widget .mk-frame.drag:active{cursor:grabbing}
	.csm-widget .mk-frame.drag:focus-visible{outline:3px solid var(--csm-ink);outline-offset:2px}
	.csm-widget .mk-grip{position:absolute;right:6px;bottom:6px;display:grid;place-items:center;width:28px;height:28px;border-radius:50%;background:rgba(0,0,0,.6);color:#fff;font-size:16px;pointer-events:none}
	.csm-widget .mk-shade{position:absolute;inset:0;background:linear-gradient(180deg,rgba(0,0,0,.35),rgba(0,0,0,0) 40%);pointer-events:none}
	.csm-widget .mk-plate{position:absolute;right:3.4%;bottom:2.7%;font:800 28.5cqw/.8 var(--csm-f-display);color:transparent;-webkit-text-stroke:max(1px,.35cqw) #fff;opacity:.9;pointer-events:none}
	.csm-widget .mk-ph-state{margin:0;font:600 12px var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .mk-ph-state.on{color:var(--csm-live)}
	.csm-widget .mk-ph-btns{display:flex;flex-wrap:wrap;gap:6px;margin-top:auto}
	/* « Enlever la photo / le logo » asks in its place (5.19.0): the question, « Oui, enlever », « Non ». */
	.csm-widget .mk-drop{align-items:center}
	.csm-widget .mk-drop b{flex-basis:100%;font:700 13px var(--csm-f-label);color:var(--csm-ink)}
	.csm-widget .mk-small.mk-drop-yes{border-color:var(--csm-dnf);color:var(--csm-dnf)}
	.csm-widget .mk-small.mk-drop-yes:hover{background:var(--csm-dnf);color:#fff}
	@media (max-width:600px){
	.csm-widget :is(.mk-ph-cover,.mk-ph-offer,.mk-ph-profileLeft,.mk-ph-profileCenter,.mk-ph-profileRight){flex-basis:100%}
	.csm-widget .mk-ph-portrait{flex-basis:45%}
	}
	/* « Voir un exemple » under the personal texts (5.12.0, frozen mockup design/media-kit-examples-mockup/, variant B). */
	.csm-widget .mk-ex-see{display:inline-flex;align-items:center;min-height:32px;margin:2px 0 6px;padding:0;border:0;background:none;color:#7a5f14;font:700 14px/1.2 var(--csm-f-label);cursor:pointer;text-decoration:underline;text-underline-offset:3px}
	.csm-widget .mk-ex-see:hover{color:var(--csm-ink)}
	.csm-widget .mk-ex{display:grid;gap:12px;margin:0 0 10px;padding:14px 16px;border:1px solid #e3cf8f;border-left:4px solid var(--csm-gold);border-radius:var(--csm-radius);background:var(--csm-gold-bg)}
	.csm-widget .mk-ex-lead{margin:0;font:600 13px/1.4 var(--csm-f-label);color:#7a5f14}
	.csm-widget .mk-ex-text{margin:0;font:400 15px/1.55 var(--csm-f-data);color:var(--csm-ink)}
	.csm-widget .mk-ex-lines{margin:0;padding-left:1.3em;display:grid;gap:4px;font:400 15px/1.5 var(--csm-f-data);color:var(--csm-ink)}
	.csm-widget .mk-ex-blank{background:#fff;border:1px dashed #b8952e;border-radius:4px;padding:0 3px;color:#7a5f14;font-style:italic}
	.csm-widget .mk-ex-q{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:6px 12px;margin:0;padding:6px 0;border-bottom:1px solid #eadba8;font:italic 400 15px/1.45 var(--csm-f-data);color:var(--csm-ink)}
	.csm-widget .mk-ex-q:last-child{border-bottom:0}
	.csm-widget .mk-ex-use{justify-self:start}
	.csm-widget .mk-ex-ask{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;padding:10px 12px;border:1px solid var(--csm-ink);border-radius:var(--csm-radius);background:#fff}
	.csm-widget .mk-ex-ask b{flex-basis:100%;font:700 14px var(--csm-f-label);color:var(--csm-ink)}
	.csm-widget .mk-ex-left{margin:6px 0 0;font:700 13px/1.4 var(--csm-f-label);color:#7a5f14}
	@media (pointer:coarse){.csm-widget .mk-ex-see{min-height:44px}}
	.csm-widget .mk-row{margin-bottom:12px;padding:14px;border:1px solid var(--csm-line);border-radius:var(--csm-radius);background:var(--csm-panel)}
	.csm-widget .mk-row-h{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px;font:800 12px var(--csm-f-label);letter-spacing:.08em;text-transform:uppercase;color:var(--csm-ink)}
	/* « Au-delà du classement » and « Langues parlées » (5.20.0, frozen mockup design/media-kit-beyond-mockup/).
	 * A sub-part of a section (« Faits marquants », « Saisons précédentes », « Implication »). */
	.csm-widget .mb-part{margin-top:22px;padding-top:16px;border-top:1px solid var(--csm-line)}
	.csm-widget .mb-part:first-of-type{border-top:0;padding-top:0}
	.csm-widget .mb-part>.mk-hint{margin-bottom:10px;max-width:75ch}
	.csm-widget .mb-declared{margin:0 0 6px;padding:10px 12px;border-left:3px solid var(--csm-muted);background:var(--csm-panel);font:600 13px/1.45 var(--csm-f-label);color:var(--csm-text);max-width:75ch}
	.csm-widget .mb-old{margin:0 0 12px}
	.csm-widget .mb-add{margin-top:10px;align-self:flex-start}
	/* An earlier result: the year and the place, then the class and the championship in each language. */
	.csm-widget .mb-yr{display:grid;grid-template-columns:repeat(2,minmax(0,140px));gap:14px 18px}
	.csm-widget .mb-yr .mk-field{margin-top:0}
	.csm-widget .mb-pair{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.6fr);gap:10px}
	.csm-widget .mb-pair .mk-field{margin-top:4px}
	/* The languages spoken: a language, a level, « Retirer ». */
	.csm-widget .mb-lrow{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr) auto;gap:8px;align-items:center;margin-top:8px;max-width:620px}
	.csm-widget .mb-sel{display:block;width:100%;box-sizing:border-box;min-height:44px;padding:10px 12px;border:1px solid var(--csm-line-strong);border-radius:8px;background:var(--csm-bg);color:var(--csm-ink);font:500 14px/1.45 var(--csm-f-label)}
	.csm-widget .mb-sel:invalid{color:var(--csm-muted)}
	.csm-widget .mb-sel option{color:var(--csm-ink)}
	.csm-widget .mb-sel:focus{outline:2px solid var(--csm-ink);outline-offset:0;border-color:var(--csm-ink)}
	@media (max-width:640px){
		.csm-widget .mb-pair{grid-template-columns:minmax(0,1fr)}
		.csm-widget .mb-lrow{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}
		.csm-widget .mb-lrow .mk-small{grid-column:1/3;justify-self:start}
		.csm-widget .mb-sel{font-size:16px}
	}
	/* The bar is fixed, not sticky: a theme's overflow:hidden around the page (The7's #page) stops a sticky one. Its
	 * contents in the page's column, the right end clear of « Haut de page »; the form's end clear of the bar. */
	.csm-widget .mk-bar{position:fixed;left:0;right:0;bottom:0;z-index:30;display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:10px;margin:0;padding:12px max(72px,calc((100% - 1120px) / 2 + 24px)) max(12px,env(safe-area-inset-bottom)) max(24px,calc((100% - 1120px) / 2 + 24px));border-top:1px solid var(--csm-line);background:rgba(255,255,255,.96);backdrop-filter:blur(6px);box-shadow:0 -6px 18px rgba(18,20,23,.08)}
	.csm-widget .mk-view:has(>.mk-bar){padding-bottom:96px}
	.csm-widget .mk-clear{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;margin:0 0 18px;padding:14px 16px;border:1px solid var(--csm-dnf);border-left-width:4px;border-radius:var(--csm-radius);background:#fff}
	.csm-widget .mk-clear b{flex-basis:100%;font:800 15px var(--csm-f-label);color:var(--csm-dnf)}
	.csm-widget .mk-clear p{flex-basis:100%;margin:0;font:500 14px/1.5 var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .mk-clear label{display:flex;flex-wrap:wrap;align-items:center;gap:8px;font:700 13px var(--csm-f-label);color:var(--csm-ink)}
	.csm-widget .mk-clear input{box-sizing:border-box;width:12em;min-height:42px;padding:0 12px;border:1px solid var(--csm-line-strong);border-radius:var(--csm-radius);background:var(--csm-bg);color:var(--csm-ink);font:700 14px var(--csm-f-label);letter-spacing:.06em}
	.csm-widget .mk-clear input:focus-visible{outline:2px solid var(--csm-ink);outline-offset:1px}
	.csm-widget .mk-main.mk-danger:not([disabled]){background:var(--csm-dnf);border-color:var(--csm-dnf)}
	.csm-widget .mk-main.mk-danger:not([disabled]):hover{background:#8f2f25}
	.csm-widget .mk-status{flex:1 1 220px;font:600 13px/1.4 var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .mk-status.err{color:var(--csm-dnf)}
	.csm-widget .mk-status.ok{color:var(--csm-live)}
	.csm-widget .mk-done{margin-top:6px;padding:22px;border:1px solid #e3cf8f;border-left:4px solid var(--csm-gold);border-radius:var(--csm-radius);background:var(--csm-gold-bg);max-width:70ch}
	.csm-widget .mk-done h2{margin:0 0 10px;font:800 italic 26px/1.05 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink)}
	.csm-widget .mk-done p{margin:0 0 10px;font:500 14px/1.5 var(--csm-f-label);color:var(--csm-text)}
	@media (max-width:640px){
		.csm-widget .mk-steps{grid-template-columns:1fr}
		.csm-widget .mk-seg{display:flex}
		.csm-widget .mk-seg button{flex:1 1 0;padding:0 8px}
		.csm-widget .mk-bar{gap:8px;padding:10px 72px max(10px,env(safe-area-inset-bottom)) 16px}
		.csm-widget .mk-view:has(>.mk-bar){padding-bottom:150px}
		.csm-widget .mk-status{flex-basis:100%}
		.csm-widget .mk-bar .mk-main{flex:1 1 auto;justify-content:center;padding:0 12px}
		.csm-widget .mk-bar .mk-sec-b{flex:0 0 auto}
	}
	@media (pointer:coarse){
		.csm-widget :is(.mk-in :is(input,textarea),.mk-clear input,.mb-sel){font-size:16px}
	}
	.csm-widget a.follow{text-decoration:none}
	.csm-widget .mk-wait{margin:24px 0;font:600 14px/1.5 var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .mk-wait.err{color:var(--csm-dnf)}
	/* The rider's code before the form (user, 2026-10-18; the Palmarès's code form, in the card's colours). */
	.csm-widget .mk-gate-box{max-width:560px;margin-top:18px;padding:20px 22px;border:1px solid #e3cf8f;border-left:4px solid var(--csm-gold);border-radius:var(--csm-radius);background:var(--csm-gold-bg)}
	.csm-widget .mk-gate-box .eyebrow{margin-bottom:4px;color:#7a5f14}
	.csm-widget .mk-gate-box h2{margin:0 0 6px;font:800 italic 24px var(--csm-f-display);color:var(--csm-ink)}
	.csm-widget .mk-gate-box p{margin:0;font:500 14px/1.45 var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .mk-code{display:flex;flex-wrap:wrap;align-items:flex-end;gap:12px;margin-top:16px}
	.csm-widget .mk-code label{display:flex;flex-direction:column;gap:6px;font:800 11px var(--csm-f-label);letter-spacing:.14em;text-transform:uppercase;color:#7a5f14}
	.csm-widget .mk-code input{width:min(260px,70vw);min-height:42px;box-sizing:border-box;padding:10px 14px;border:1px solid var(--csm-line-strong);border-radius:10px;background:var(--csm-bg);font:600 16px var(--csm-f-label);letter-spacing:normal;text-transform:none;color:var(--csm-ink)}
	.csm-widget .mk-code input:focus-visible,.csm-widget .mk-code button:focus-visible{outline:2px solid var(--csm-ink);outline-offset:2px}
	.csm-widget .mk-gate-box .mk-wrong{margin-top:12px;font-weight:700;color:var(--csm-dnf)}
	.csm-widget .mk-gate-box .mk-lost{margin-top:14px;font-size:13px;color:var(--csm-muted)}
	.csm-widget .mk-size{flex:0 1 auto;font:600 12px/1.4 var(--csm-f-label);color:var(--csm-muted)}
	.csm-widget .mk-size.err{color:var(--csm-dnf)}
	/* « ✨ Remplir avec une IA » (5.16.0, frozen mockup design/media-kit-ai-mockup/mockup-v1.html). */
	.csm-widget .mk-ai{border:1px solid #e3cf8f;border-left:4px solid var(--csm-gold);border-radius:var(--csm-radius);background:var(--csm-gold-bg);padding:14px 16px}
	.csm-widget .mk-ai-h{margin:0}
	.csm-widget .mk-ai-toggle{display:flex;align-items:center;gap:10px;width:100%;min-height:44px;padding:0;border:0;background:none;text-align:left;cursor:pointer;color:var(--csm-ink)}
	.csm-widget .mk-ai-t{font:800 italic 20px/1.1 var(--csm-f-display);letter-spacing:.01em;text-transform:uppercase}
	.csm-widget .mk-ai-toggle .aside{margin-left:auto;font:600 12px var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .mk-ai-chev{width:10px;height:10px;border-right:2px solid var(--csm-ink);border-bottom:2px solid var(--csm-ink);transform:rotate(45deg);margin:0 6px 4px 4px;transition:transform .15s}
	.csm-widget .mk-ai.open .mk-ai-chev{transform:rotate(-135deg);margin-bottom:-4px}
	.csm-widget .mk-ai-toggle:focus-visible{outline:2px solid var(--csm-ink);outline-offset:4px;border-radius:4px}
	.csm-widget .mk-ai-lead{margin:0 0 12px;font:500 14px/1.5 var(--csm-f-label);color:var(--csm-text);max-width:70ch}
	.csm-widget .mk-ai:not(.open) .mk-ai-lead{margin-bottom:0}
	.csm-widget .mk-ai-steps{list-style:none;margin:0;padding:0;display:grid;gap:14px}
	.csm-widget .mk-ai-steps>li{display:grid;grid-template-columns:28px 1fr;gap:10px;align-items:start}
	.csm-widget .mk-ai-steps>li>b{display:grid;place-items:center;width:28px;height:28px;border-radius:50%;background:var(--csm-ink);color:#fff;font:800 13px var(--csm-f-label)}
	.csm-widget .mk-ai-steps>li>div{min-width:0}
	.csm-widget .mk-ai-steps p{margin:3px 0 8px;font:600 14px/1.45 var(--csm-f-label);color:var(--csm-ink)}
	.csm-widget .mk-ai-row{display:flex;flex-wrap:wrap;gap:8px}
	.csm-widget .mk-ai-next{margin-top:10px;padding:12px 14px;border:1px solid var(--csm-live);border-left-width:4px;border-radius:var(--csm-radius);background:#fff}
	.csm-widget .mk-ai-next>b{display:block;margin-bottom:6px;font:800 15px var(--csm-f-label);color:var(--csm-live)}
	.csm-widget .mk-ai-next ol{margin:0;padding-left:1.3em;display:grid;gap:8px;font:600 14px/1.5 var(--csm-f-label);color:var(--csm-ink)}
	.csm-widget .mk-ai-links{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}
	.csm-widget .mk-ai-links a{display:inline-flex;align-items:center;min-height:36px;padding:0 14px;border:1px solid var(--csm-ink);border-radius:999px;color:var(--csm-ink);font:700 13px var(--csm-f-label);text-decoration:none}
	.csm-widget .mk-ai-links a:hover{background:var(--csm-ink);color:#fff}
	.csm-widget .mk-ai-see{display:inline-flex;align-items:center;min-height:32px;margin-top:8px;padding:0;border:0;background:none;cursor:pointer;font:700 13px var(--csm-f-label);color:#7a5f14;text-decoration:underline;text-underline-offset:3px}
	.csm-widget .mk-ai-text{max-height:320px;overflow:auto;margin:6px 0 0;padding:10px 12px;border:1px solid var(--csm-line-strong);border-radius:8px;background:var(--csm-bg);font:400 12px/1.5 ui-monospace,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere;color:var(--csm-ink)}
	.csm-widget .mk-ai-reply-l{display:block;margin-bottom:8px}
	.csm-widget .mk-ai-reply{display:block;width:100%;box-sizing:border-box;padding:10px 12px;border:1px solid var(--csm-line-strong);border-radius:8px;background:var(--csm-bg);color:var(--csm-ink);font:400 13px/1.45 ui-monospace,Consolas,monospace;resize:vertical}
	.csm-widget .mk-ai-reply:focus{outline:2px solid var(--csm-ink);border-color:var(--csm-ink)}
	.csm-widget .mk-ai-status{margin:10px 0 0;font:700 14px/1.45 var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .mk-ai-status.ok{color:var(--csm-live)}
	.csm-widget .mk-ai-status.err{color:var(--csm-dnf)}
	.csm-widget .mk-ai-ask{margin-top:10px}
	.csm-widget .mk-ai-list{flex-basis:100%;margin:0;padding-left:1.2em;font:500 14px/1.5 var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .mk-ai-privacy{margin:14px 0 0;font:500 12px/1.45 var(--csm-f-label);color:var(--csm-text)}
	@media (pointer:coarse){.csm-widget .mk-ai-reply{font-size:16px}}

	/* The media kit's « Réglages du dossier » and « 👁 Aperçu du PDF » (5.18.0, frozen mockup design/media-kit-preview-mockup/) */
	.csm-widget .mk-sub{margin:18px 0 8px;font:700 14px var(--csm-f-label);color:var(--csm-ink)}
	.csm-widget .mk-settings .mk-sub:first-of-type{margin-top:4px}
	.csm-widget .mk-check{display:flex;align-items:center;gap:10px;font:600 15px/1.4 var(--csm-f-label);color:var(--csm-ink);cursor:pointer}
	.csm-widget .mk-check input{width:20px;height:20px;margin:0;accent-color:var(--csm-ink);flex:none}
	.csm-widget .mk-check+.mk-hint{margin-top:6px}
	.csm-widget .mk-pv-short{display:none}
	.csm-widget .mk-pv{position:fixed;inset:0;z-index:100;display:flex;flex-direction:column;background:#3b3f46}
	.csm-widget .mk-pv-top{display:flex;flex-wrap:wrap;align-items:center;gap:10px 16px;padding:12px 16px;background:var(--csm-bg,#fff);border-bottom:1px solid var(--csm-line-strong)}
	.csm-widget .mk-pv-top h2{margin:0;font:800 20px/1.1 var(--csm-f-display);text-transform:uppercase;color:var(--csm-ink)}
	.csm-widget .mk-pv-count{font:600 13px var(--csm-f-label);color:var(--csm-text)}
	.csm-widget .mk-pv-close{margin-left:auto}
	.csm-widget .mk-pv-zoom[hidden]{display:none}
	.csm-widget .mk-pv-zoom button,.csm-widget .mk-pv-langs button{white-space:nowrap}
	.csm-widget .mk-pv-diff{background:#fff4cc;color:#3d3200;border-bottom:2px solid #e0c34a;font:500 14px/1.45 var(--csm-f-label)}
	.csm-widget .mk-pv-diff summary{cursor:pointer;padding:10px 16px;font-size:16px;min-height:24px}
	.csm-widget .mk-pv-diff p,.csm-widget .mk-pv-diff ul{margin:0;padding:0 16px 8px 34px}
	.csm-widget .mk-pv-diff p{padding-left:16px}
	.csm-widget .mk-pv-diff li{margin:2px 0}
	.csm-widget .mk-pv-diff li.err{color:#b3261e;font-weight:700}
	.csm-widget .mk-pv-more{font-weight:500;text-decoration:underline;white-space:nowrap}
	.csm-widget .mk-pv-scroll{flex:1;overflow:auto;padding:18px 12px;-webkit-overflow-scrolling:touch}
	.csm-widget .mk-pv-sheet{margin:0 auto;overflow:hidden}
	.csm-widget .mk-pv-frame{display:block;border:0;background:transparent;transform-origin:0 0}
	@media (max-width:640px){
		.csm-widget .mk-bar .mk-pv-long{display:none}
		.csm-widget .mk-bar .mk-pv-short{display:inline}
		.csm-widget .mk-bar .mk-size{flex:1 1 100%;font-size:12px;line-height:1.2}
		.csm-widget .mk-bar .mk-sec-b{flex:1 1 0;justify-content:center}
		.csm-widget .mk-pv-count{display:none}
		.csm-widget .mk-pv-langs,.csm-widget .mk-pv-zoom{flex:1 1 auto}
		.csm-widget .mk-pv-langs button,.csm-widget .mk-pv-zoom button{padding:0 10px;font-size:13px}
	}
	@media (max-width:600px){
		.csm-widget .mk-pv-top{padding:10px 12px}
		.csm-widget .mk-pv-top h2{font-size:17px;flex:1}
		.csm-widget .mk-pv-close{margin-left:0;order:1}
		.csm-widget .mk-pv-langs,.csm-widget .mk-pv-zoom{order:2}
		.csm-widget .mk-pv-count{order:3}
		.csm-widget .mk-pv-diff{font-size:13px}
		.csm-widget .mk-pv-diff summary{padding:8px 12px;font-size:15px}
		.csm-widget .mk-pv-scroll{padding:12px 8px}
	}
	</script>
	<script>
// GENERATED by `npm run build` from src/site and src/engine: edit there, not here.
"use strict";
(() => {
  var __defProp = Object.defineProperty;
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);

  // plugins/csm-standings/src/engine/issues.ts
  function blocksSheet(issues3) {
    return issues3.some((i) => i.severity === "error" && i.category === void 0);
  }
  function blockedCategories(issues3) {
    const blocked = /* @__PURE__ */ new Set();
    for (const i of issues3) if (i.severity === "error" && i.category !== void 0) blocked.add(i.category);
    return blocked;
  }
  function errorsFirst(issues3) {
    const rank = (i) => i.severity === "error" ? 0 : 1;
    return issues3.sort((a, b) => rank(a) - rank(b));
  }

  // plugins/csm-standings/src/engine/sponsors.ts
  var SPONSORS_TAB = "SPONSORS";
  var SPONSORS_HEADERS = ["SEASON", "NAME", "LOGO"];
  var TIERS = ["PLATINUM", "GOLD", "SILVER", "BRONZE", "A LA CARTE"];
  var isTier = (s) => TIERS.includes(s);
  var YEAR_RE = /^\d{4}$/;
  function readSponsors(rows3, seasons, galaSeasons, logos) {
    const lists = /* @__PURE__ */ new Map();
    if (rows3 === null)
      return {
        lists,
        issues: galaSeasons.length ? [{ severity: "warning", code: "sponsors-missing", seasons: [...galaSeasons] }] : null
      };
    const header2 = rows3[0] ?? [];
    const issues3 = [];
    for (const expected of SPONSORS_HEADERS)
      if (!header2.includes(expected)) issues3.push({ severity: "error", code: "sponsors-header", row: 1, expected });
    if (issues3.length) return { lists, issues: issues3 };
    const col = (h) => header2.indexOf(h);
    const kept = [];
    for (const [i, cells] of rows3.entries()) {
      if (i === 0 || cells.every((c) => c === "")) continue;
      const row2 = i + 1;
      const get = (h) => cells[col(h)] ?? "";
      const at2 = (h, severity, issue) => {
        issues3.push({ row: row2, col: col(h), column: h, severity, ...issue });
      };
      const season = get("SEASON");
      const name2 = get("NAME");
      if (season === "" || seasons !== null && !seasons.includes(season)) {
        at2(
          "SEASON",
          "error",
          season === "" ? { code: "cell-empty" } : { code: "sponsors-season-unknown", found: season }
        );
        continue;
      }
      if (name2 === "") {
        at2("NAME", "error", { code: "cell-empty" });
        continue;
      }
      const first = kept.find((k) => k.season === season && k.sponsor.name === name2);
      if (first) {
        at2("NAME", "warning", { code: "sponsor-twice", found: name2, firstRow: first.sponsor.row });
        continue;
      }
      const logo2 = get("LOGO");
      if (!logos.includes(logo2)) at2("LOGO", "warning", { code: "logo-unknown", found: logo2 });
      const tier = get("TIER");
      if (tier !== "" && !isTier(tier)) at2("TIER", "warning", { code: "tier-invalid", found: tier });
      const since = get("SINCE");
      let year = null;
      if (since !== "") {
        if (!YEAR_RE.test(since)) at2("SINCE", "warning", { code: "since-invalid", found: since });
        else if (Number(since) > Number(season)) at2("SINCE", "warning", { code: "since-after", found: since, season });
        else year = Number(since);
      }
      kept.push({
        season,
        sponsor: {
          row: row2,
          name: name2,
          logo: logos.includes(logo2) ? logo2 : null,
          tier: isTier(tier) ? tier : null,
          since: year
        }
      });
    }
    for (const { season, sponsor } of kept) {
      const list3 = lists.get(season) ?? [];
      list3.push(sponsor);
      lists.set(season, list3);
    }
    for (const season of galaSeasons)
      if (!lists.has(season)) issues3.push({ severity: "warning", code: "sponsors-none", season });
    return { lists, issues: errorsFirst(issues3) };
  }

  // plugins/csm-standings/src/engine/text.ts
  var ACCENTS = {
    A: "àáâãäåÀÁÂÃÄÅ",
    C: "çÇ",
    E: "èéêëÈÉÊË",
    I: "ìíîïÌÍÎÏ",
    N: "ñÑ",
    O: "òóôõöøÒÓÔÕÖØ",
    U: "ùúûüÙÚÛÜ",
    Y: "ýÿÝŸ",
    AE: "æÆ",
    OE: "œŒ"
  };
  function riderKey(name2) {
    let key = name2;
    for (const [plain, accented] of Object.entries(ACCENTS)) {
      for (const letter of accented) key = key.split(letter).join(plain);
    }
    key = key.replace(/[a-z]+/g, (s) => s.toUpperCase());
    return key.replace(/[ \t]+/g, " ").replace(/^ | $/g, "");
  }
  function hasExtraSpaces(text) {
    return text !== text.trim() || /\s\s/.test(text);
  }
  function colLetter(colIdx) {
    let s = "";
    let n = colIdx + 1;
    while (n > 0) {
      const m = (n - 1) % 26;
      s = String.fromCharCode(65 + m) + s;
      n = Math.floor((n - 1) / 26);
    }
    return s;
  }
  var naturalOrder = new Intl.Collator("fr", { numeric: true }).compare;
  function editDistance(a, b) {
    let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
    for (let i = 1; i <= a.length; i++) {
      const cur = [i];
      for (let j = 1; j <= b.length; j++) {
        cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
      }
      prev = cur;
    }
    return prev[b.length];
  }

  // plugins/csm-standings/src/engine/championships.ts
  var POINTS_SCALE = [25, 22, 20, 18, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1];
  var RULEBOOKS = {
    "2026": {
      pdf: "Livre-de-reglements-2026.pdf",
      championships: {
        CSM: { joker: { enabled: false } },
        CP: { joker: { enabled: false } },
        SBQ: { joker: { enabled: false } },
        SMO: { joker: { enabled: false } },
        SMQ: {
          // Art. 19.4 IMPORTANT 2: the season counts 5 of the 6 rounds with a
          // dirt section (rounds 1-2-3-4-5-7), SM 1 to SM 7 only, never
          // Shannonville.
          joker: {
            enabled: true,
            categories: ["SM 1", "SM 2", "SM 3", "SM 4", "SM 5", "SM 6", "SM 7"],
            eligibleEvents: ["SHANNONVILLE", "MECAGLISSE", "SKRDL", "SHAWINIGAN", "GP3R-S", "GP3R-D", "KTR"],
            // GP3R-S and GP3R-D are two separate events, each its own Joker
            // candidate (organizer, 2026-09-24).
            protectedEvents: ["SHANNONVILLE"]
          }
        }
      },
      // Art. 1.9–1.11: Supermoto 1 (Pro), 2 (Inter), 3 (Amateur); art. 1.25–1.28: Supersport and Superbike 1 (Pro),
      // 2 (Amateur). Art. 1.5–1.7: a rider moves up for the next season, never back down.
      levels: {
        SM: ["AMATEUR", "INTER", "PRO"],
        SS: ["AMATEUR", "PRO"],
        SBK: ["AMATEUR", "PRO"]
      }
    }
  };

  // plugins/csm-standings/src/engine/championship.ts
  var own = (o, key) => Object.prototype.hasOwnProperty.call(o, key);
  function isEdition(rulebook) {
    return own(RULEBOOKS, rulebook);
  }
  function championshipOf(rulebook, code) {
    if (!own(RULEBOOKS, rulebook)) return null;
    const championships = RULEBOOKS[rulebook].championships;
    return own(championships, code) ? { code, rulebook, config: championships[code] } : null;
  }
  function pointsScaleOf(championship2) {
    return championship2.config.pointsScale ?? POINTS_SCALE;
  }
  function configWarnings(championship2, table2) {
    const joker = championship2.config.joker;
    if (joker?.enabled !== true) return [];
    const classes = table2.categories.map((c) => c.name);
    const warnings = [];
    for (const found of joker.categories ?? []) {
      if (!classes.includes(found)) warnings.push({ severity: "warning", code: "joker-class-missing", found });
    }
    for (const found of /* @__PURE__ */ new Set([...joker.eligibleEvents ?? [], ...joker.protectedEvents ?? []])) {
      if (!table2.events.includes(found)) warnings.push({ severity: "warning", code: "joker-event-missing", found });
    }
    return warnings;
  }

  // plugins/csm-standings/src/engine/config.ts
  var SEASONS_TAB = "SEASONS";
  var VENUES_TAB = "VENUES";
  var CONFIG_TAB = "CONFIG";
  var CALENDAR_TAB = "CALENDAR";
  var SEASONS_HEADERS = ["SEASON", "RULEBOOK", "RULEBOOK URL"];
  var VENUES_HEADERS = ["CODE", "NAME", "ADDRESS", "COORDINATES"];
  var CONFIG_HEADERS = [
    "SITE ORDER",
    "SEASON",
    "CODE",
    "TAB",
    "NAME",
    "LOGO",
    "COLOR",
    "SITE CLASS ORDER",
    "GALA ORDER"
  ];
  var CALENDAR_HEADERS = [
    "SEASON",
    "EVENT",
    "START",
    "END",
    "CHAMPIONSHIPS",
    "VENUE CODE",
    "SCHEDULE",
    "RACE DAYS"
  ];
  var DEFAULT_COLOR = "#121417";
  var URL_RE = /^https?:\/\/\S+$/;
  var COLOR_RE = /^#[0-9A-Fa-f]{6}$/;
  var INTEGER_RE = /^\d+$/;
  var LIST_SEPARATOR = /\s*,\s*/;
  var splitList = (text) => text === "" ? [] : text.split(LIST_SEPARATOR);
  function isIsoDate(text) {
    if (!/^\d{4}-\d{2}-\d{2}$/.test(text)) return false;
    const [y, m, d] = text.split("-").map(Number);
    const date = new Date(Date.UTC(y, m - 1, d));
    return date.getUTCFullYear() === y && date.getUTCMonth() === m - 1 && date.getUTCDate() === d;
  }
  var isEmptyRow = (cells) => cells.every((c) => c === "");
  function columnsOf(tab, rows3, headers, optional = []) {
    if (rows3 === null) return { issues: [{ severity: "error", code: "tab-missing", found: tab }] };
    const header2 = rows3[0] ?? [];
    const cols = {};
    const issues3 = [];
    for (const expected of headers) {
      const col = header2.indexOf(expected);
      if (col === -1 && !optional.includes(expected))
        issues3.push({ severity: "error", code: "header-missing", row: 1, expected });
      else cols[expected] = col;
    }
    return issues3.length ? { issues: issues3 } : { cols };
  }
  function languageColumns(rows3, bases, languages, issues3) {
    const found = [];
    rows3[0].forEach((header2, col) => {
      const m = /^(.+) ([A-Z]{2})$/.exec(header2);
      if (!m || !bases.includes(m[1])) return;
      const lang = m[2].toLowerCase();
      if (languages && !languages.includes(lang))
        issues3.push({
          severity: "warning",
          row: 1,
          col,
          column: header2,
          code: "language-unknown",
          found: m[2],
          languages
        });
      else found.push({ base: m[1], lang, col, header: header2 });
    });
    return found;
  }
  function inLanguages(columns, base, cells, row2, issues3, isUrl) {
    const texts = {};
    for (const c of columns) {
      const text = cells[c.col] ?? "";
      if (c.base !== base || text === "") continue;
      if (isUrl && !URL_RE.test(text))
        issues3.push({ severity: "warning", row: row2, col: c.col, column: c.header, code: "url-invalid", found: text });
      else texts[c.lang] = text;
    }
    return texts;
  }
  function dataRows(rows3, cols) {
    const out = [];
    rows3.forEach((cells, i) => {
      if (i === 0 || isEmptyRow(cells)) return;
      const row2 = i + 1;
      out.push({
        row: row2,
        cells,
        get: (h) => cells[cols[h]] ?? "",
        // An optional column that is missing has no letter.
        where: (h) => cols[h] === -1 ? { row: row2, column: h } : { row: row2, col: cols[h], column: h }
      });
    });
    return out;
  }
  var COORDINATES_RE = /^(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)$/;
  function pointOf(text) {
    const m = COORDINATES_RE.exec(text);
    if (!m || Math.abs(Number(m[1])) > 90 || Math.abs(Number(m[2])) > 180) return null;
    return m[1] + "," + m[2];
  }
  function readSeasons(rows3, languages) {
    const found = columnsOf(SEASONS_TAB, rows3, SEASONS_HEADERS);
    if ("issues" in found) return { blocked: true, entries: [], issues: found.issues };
    const issues3 = [];
    const langColumns = languageColumns(rows3, ["RULEBOOK URL"], languages, issues3);
    const entries = [];
    for (const { row: row2, cells, get, where } of dataRows(rows3, found.cols)) {
      const at2 = (h, severity, issue) => {
        issues3.push({ ...where(h), severity, ...issue });
      };
      const season = get("SEASON");
      if (season === "") at2("SEASON", "error", { code: "cell-empty" });
      else if (!INTEGER_RE.test(season)) at2("SEASON", "error", { code: "season-invalid", found: season });
      if (!INTEGER_RE.test(season)) continue;
      const first = entries.find((e) => e.season === season);
      if (first) {
        at2("SEASON", "error", { code: "season-twice", found: season, firstRow: first.row });
        continue;
      }
      const rulebook = get("RULEBOOK");
      if (rulebook === "") at2("RULEBOOK", "error", { code: "cell-empty" });
      else if (!isEdition(rulebook)) at2("RULEBOOK", "error", { code: "rulebook-unknown", found: rulebook });
      const urlText = get("RULEBOOK URL");
      const rulebookUrl = URL_RE.test(urlText) ? urlText : null;
      if (rulebookUrl === null)
        at2("RULEBOOK URL", "warning", urlText === "" ? { code: "cell-empty" } : { code: "url-invalid", found: urlText });
      entries.push({
        row: row2,
        season,
        rulebook,
        ruled: isEdition(rulebook),
        rulebookUrl,
        rulebookUrls: inLanguages(langColumns, "RULEBOOK URL", cells, row2, issues3, true)
      });
    }
    return { blocked: false, entries, issues: issues3 };
  }
  function readVenues(rows3, languages) {
    if (rows3 === null) return { blocked: true, entries: [], issues: [{ severity: "error", code: "venues-missing" }] };
    const found = columnsOf(VENUES_TAB, rows3, VENUES_HEADERS);
    if ("issues" in found)
      return {
        blocked: true,
        entries: [],
        // Its own codes: without VENUES the races show without their place, nothing else is blocked.
        issues: found.issues.map((i) => ({
          severity: "error",
          code: "venues-header",
          row: 1,
          expected: i.expected
        }))
      };
    const issues3 = [];
    const langColumns = languageColumns(rows3, ["NAME"], languages, issues3);
    const entries = [];
    for (const { row: row2, cells, get, where } of dataRows(rows3, found.cols)) {
      const at2 = (h, severity, issue) => {
        issues3.push({ ...where(h), severity, ...issue });
      };
      const code = get("CODE");
      const name2 = get("NAME");
      if (code === "") {
        at2("CODE", "error", { code: "cell-empty" });
        continue;
      }
      const first = entries.find((e) => e.code === code);
      if (first) {
        at2("CODE", "error", { code: "venue-twice", found: code, firstRow: first.row });
        continue;
      }
      if (name2 === "") {
        at2("NAME", "error", { code: "cell-empty" });
        continue;
      }
      const address = get("ADDRESS");
      if (address === "") at2("ADDRESS", "warning", { code: "cell-empty" });
      const coordinates = get("COORDINATES");
      const point = pointOf(coordinates);
      if (coordinates === "") at2("COORDINATES", "error", { code: "cell-empty" });
      else if (point === null) at2("COORDINATES", "error", { code: "coordinates-invalid", found: coordinates });
      entries.push({
        row: row2,
        code,
        name: name2,
        names: inLanguages(langColumns, "NAME", cells, row2, issues3, false),
        address,
        point
      });
    }
    return { blocked: false, entries, issues: issues3 };
  }
  function readConfig(rows3, tabNames, seasons, formatted = /* @__PURE__ */ new Set(), languages, galaSeasons = /* @__PURE__ */ new Set()) {
    const found = columnsOf(CONFIG_TAB, rows3, CONFIG_HEADERS, ["SITE CLASS ORDER", "GALA ORDER"]);
    if ("issues" in found) return { blocked: true, entries: [], issues: found.issues };
    const hasGalaOrder = found.cols["GALA ORDER"] !== -1;
    const issues3 = [];
    const langColumns = languageColumns(rows3, ["NAME"], languages, issues3);
    const entries = [];
    const firstRowOf = /* @__PURE__ */ new Map();
    const orderRowOf = /* @__PURE__ */ new Map();
    const galaRowOf = /* @__PURE__ */ new Map();
    let needsGalaOrder = false;
    for (const { row: row2, cells, get, where } of dataRows(rows3, found.cols)) {
      const error = (h, issue) => {
        issues3.push({ ...where(h), severity: "error", ...issue });
      };
      const warning = (h, issue) => {
        issues3.push({ ...where(h), severity: "warning", ...issue });
      };
      const season = get("SEASON");
      const code = get("CODE");
      if (season === "") error("SEASON", { code: "cell-empty" });
      else if (!INTEGER_RE.test(season)) error("SEASON", { code: "season-invalid", found: season });
      if (code === "") error("CODE", { code: "cell-empty" });
      if (!INTEGER_RE.test(season) || code === "") continue;
      const listed = seasons.entries.find((e) => e.season === season);
      if (!listed) {
        if (!seasons.blocked) error("SEASON", { code: "season-unknown", found: season });
        continue;
      }
      const key = season + "\n" + code;
      const firstRow = firstRowOf.get(key);
      if (firstRow !== void 0) {
        error("CODE", { code: "championship-twice", found: code, firstRow });
        continue;
      }
      firstRowOf.set(key, row2);
      let blocked = formatted.has(row2);
      const block = (h, issue) => {
        error(h, issue);
        blocked = true;
      };
      const orderText = get("SITE ORDER");
      let order = Infinity;
      if (orderText === "") block("SITE ORDER", { code: "cell-empty" });
      else if (!INTEGER_RE.test(orderText)) block("SITE ORDER", { code: "order-invalid", found: orderText });
      else {
        order = Number(orderText);
        const orderKey = season + "\n" + String(order);
        const orderRow = orderRowOf.get(orderKey);
        if (orderRow !== void 0) block("SITE ORDER", { code: "order-twice", found: orderText, firstRow: orderRow });
        else orderRowOf.set(orderKey, row2);
      }
      const galaText = get("GALA ORDER");
      let galaOrder = INTEGER_RE.test(galaText) ? Number(galaText) : null;
      if (galaSeasons.has(season)) needsGalaOrder = true;
      if (galaSeasons.has(season) && hasGalaOrder) {
        if (galaText === "") error("GALA ORDER", { code: "cell-empty" });
        else if (galaOrder === null) error("GALA ORDER", { code: "order-invalid", found: galaText });
        else {
          const galaKey = season + "\n" + galaText;
          const galaRow = galaRowOf.get(galaKey);
          if (galaRow === void 0) galaRowOf.set(galaKey, row2);
          else {
            error("GALA ORDER", { code: "order-twice", found: galaText, firstRow: galaRow });
            galaOrder = null;
          }
        }
      }
      const tab = get("TAB");
      if (tab === "") block("TAB", { code: "cell-empty" });
      else if (!tabNames.includes(tab)) block("TAB", { code: "tab-not-found", found: tab });
      const rulebook = listed.rulebook;
      const championship2 = listed.ruled ? championshipOf(rulebook, code) : null;
      if (!listed.ruled) blocked = true;
      else if (!championship2) block("CODE", { code: "rules-missing", rulebook, found: code });
      const name2 = get("NAME");
      if (name2 === "") warning("NAME", { code: "cell-empty" });
      const logoText = get("LOGO");
      const logo2 = URL_RE.test(logoText) ? logoText : null;
      if (logo2 === null)
        warning("LOGO", logoText === "" ? { code: "cell-empty" } : { code: "url-invalid", found: logoText });
      const names = inLanguages(langColumns, "NAME", cells, row2, issues3, false);
      let color = get("COLOR");
      if (!COLOR_RE.test(color)) {
        warning("COLOR", color === "" ? { code: "cell-empty" } : { code: "color-invalid", found: color });
        color = DEFAULT_COLOR;
      }
      entries.push({
        row: row2,
        order,
        galaOrder,
        season,
        code,
        tab,
        name: name2 || code,
        logo: logo2,
        rulebook,
        rulebookUrl: listed.rulebookUrl,
        names,
        rulebookUrls: listed.rulebookUrls,
        color,
        championship: championship2,
        blocked,
        classes: splitList(get("SITE CLASS ORDER"))
      });
    }
    if (needsGalaOrder && !hasGalaOrder)
      issues3.push({ severity: "error", code: "gala-order-header", row: 1, expected: "GALA ORDER" });
    return { blocked: false, entries, issues: issues3 };
  }
  function seasonsOf(seasons) {
    return seasons.entries.map((e) => e.season).sort((a, b) => Number(b) - Number(a));
  }
  function raceDaysOf(text, start, end, warn) {
    if (text === "") {
      warn({ code: "race-days-empty" });
      return [];
    }
    const days2 = text.split(LIST_SEPARATOR);
    const bad = days2.find((d) => !isIsoDate(d));
    if (bad !== void 0) warn({ code: "race-day-invalid", found: bad });
    else {
      const outside = days2.find((d) => d < start || d > end);
      if (outside !== void 0) warn({ code: "race-day-outside", found: outside, start, end });
      else if (days2.some((d, i) => i > 0 && d <= days2[i - 1])) warn({ code: "race-days-order", found: text });
      else return days2;
    }
    return [];
  }
  function readCalendar(rows3, config, seasons, venues, formatted = /* @__PURE__ */ new Set(), languages) {
    const found = columnsOf(CALENDAR_TAB, rows3, CALENDAR_HEADERS, ["SCHEDULE", "RACE DAYS", "VENUE CODE"]);
    if ("issues" in found) return { blocked: true, events: [], issues: found.issues };
    const issues3 = [];
    const placed = found.cols["VENUE CODE"] !== -1;
    if (!placed) issues3.push({ severity: "error", code: "venues-header", row: 1, expected: "VENUE CODE" });
    const events = [];
    const langColumns = languageColumns(rows3, ["SCHEDULE"], languages, issues3);
    for (const { row: row2, cells, get, where } of dataRows(rows3, found.cols)) {
      const before = issues3.length;
      const ok = () => !issues3.slice(before).some((i) => i.severity === "error");
      const error = (h, issue) => {
        issues3.push({ ...where(h), severity: "error", ...issue });
      };
      const warning = (h, issue) => {
        issues3.push({ ...where(h), severity: "warning", ...issue });
      };
      const season = get("SEASON");
      if (season === "") error("SEASON", { code: "cell-empty" });
      else if (!INTEGER_RE.test(season)) error("SEASON", { code: "season-invalid", found: season });
      const listed = seasons.entries.some((e) => e.season === season);
      if (INTEGER_RE.test(season) && !listed && !seasons.blocked)
        error("SEASON", { code: "season-unknown", found: season });
      const event = get("EVENT");
      if (event === "") error("EVENT", { code: "cell-empty" });
      const dates = ["START", "END"].map((h) => {
        const text = get(h);
        if (text === "") error(h, { code: "cell-empty" });
        else if (!isIsoDate(text)) error(h, { code: "date-invalid", found: text });
        return text;
      });
      const [start, end] = dates;
      if (ok() && start > end) error("END", { code: "dates-reversed", start, end });
      const list3 = get("CHAMPIONSHIPS");
      const codes = [];
      if (list3 === "") error("CHAMPIONSHIPS", { code: "cell-empty" });
      else if (listed) {
        for (const code of list3.split(LIST_SEPARATOR)) {
          if (config.entries.some((e) => e.season === season && e.code === code)) codes.push(code);
          else warning("CHAMPIONSHIPS", { code: "championship-unknown", found: code, season });
        }
      }
      const scheduleText = get("SCHEDULE");
      let schedule = null;
      if (URL_RE.test(scheduleText)) schedule = scheduleText;
      else if (scheduleText !== "") warning("SCHEDULE", { code: "url-invalid", found: scheduleText });
      const schedules = inLanguages(langColumns, "SCHEDULE", cells, row2, issues3, true);
      if (!ok() || formatted.has(row2)) continue;
      const raceDays3 = raceDaysOf(get("RACE DAYS"), start, end, (issue) => {
        warning("RACE DAYS", issue);
      });
      const first = events.find(
        (e) => e.season === season && e.event === event && e.codes.some((c) => codes.includes(c))
      );
      if (first) {
        const shared = first.codes.filter((c) => codes.includes(c));
        error("EVENT", { code: "event-overlap", found: event, firstRow: first.row, codes: shared });
        continue;
      }
      const venueCode = get("VENUE CODE");
      let place2 = null;
      if (placed && venueCode === "") error("VENUE CODE", { code: "cell-empty" });
      else if (placed) {
        place2 = venues.entries.find((v) => v.code === venueCode) ?? null;
        if (!place2 && !venues.blocked) error("VENUE CODE", { code: "venue-unknown", found: venueCode });
      }
      events.push({
        row: row2,
        season,
        event,
        start,
        end,
        venue: place2?.name ?? "",
        place: place2,
        codes,
        schedule,
        schedules,
        raceDays: raceDays3
      });
    }
    events.sort(
      (a, b) => a.start < b.start ? -1 : a.start > b.start ? 1 : a.end < b.end ? -1 : a.end > b.end ? 1 : a.row - b.row
    );
    return { blocked: false, events, issues: issues3 };
  }
  function entryIn(entry, lang) {
    return {
      ...entry,
      name: entry.names[lang] ?? entry.name,
      rulebookUrl: entry.rulebookUrls[lang] ?? entry.rulebookUrl
    };
  }
  function eventIn(event, lang) {
    const place2 = event.place && { ...event.place, name: event.place.names[lang] ?? event.place.name };
    return { ...event, schedule: event.schedules[lang] ?? event.schedule, place: place2, venue: place2?.name ?? "" };
  }
  function seasonIn(season, lang) {
    return { ...season, rulebookUrl: season.rulebookUrls[lang] ?? season.rulebookUrl };
  }

  // plugins/csm-standings/src/engine/constants.ts
  var LABEL_CATEGORY = "CATEGORY";
  var LABEL_PLATE = "#";
  var LABEL_NAME = "NAME";
  var FIXED_LABELS = [LABEL_CATEGORY, LABEL_PLATE, LABEL_NAME];
  var CATEGORY_COL = 0;
  var PLATE_COL = 1;
  var NAME_COL = 2;
  var FIRST_EVENT_COL = 3;
  var COLUMN_RANK = "RANKING";
  var COLUMN_PLATE = "#";
  var COLUMN_NAME = "NOM";
  var COLUMN_TOTAL = "OVERALL";
  var COLUMN_JOKER = "JOKER";
  var COLUMN_NET_TOTAL = "TOTAL JOKER";
  var RESERVED_EVENT_NAMES = [
    ...FIXED_LABELS,
    COLUMN_RANK,
    COLUMN_NAME,
    COLUMN_TOTAL,
    COLUMN_JOKER,
    COLUMN_NET_TOTAL
  ];
  var SPECIAL_VALUES = ["DNF", "DNS", "X", "N/A"];
  var NOT_HELD = "N/A";
  var ABSENT = "X";
  var RACED_WITHOUT_POINTS = ["DNF", "DNS"];

  // plugins/csm-standings/src/engine/cross-checks.ts
  function looseKey(text) {
    return riderKey(text).replace(/[\s-]+/g, "");
  }
  function crossChecks(tabs2, calendar) {
    const errors = [];
    const warnings = [];
    const blocks = /* @__PURE__ */ new Map();
    const blockOf = (code) => {
      let b = blocks.get(code);
      if (!b) blocks.set(code, b = { all: false, classes: /* @__PURE__ */ new Set() });
      return b;
    };
    const readable = tabs2.filter((t) => t.table.events.length > 0);
    calendarChecks(readable, calendar, errors, warnings, blockOf);
    spellingChecks(readable, warnings);
    sharedRoundChecks(readable, calendar, errors, warnings, blockOf);
    return { issues: [...errors, ...warnings], blocks };
  }
  function calendarChecks(tabs2, calendar, errors, warnings, blockOf) {
    for (const { code, tab, table: table2 } of tabs2) {
      const starts = [];
      const dated = [];
      for (const event of table2.events) {
        const race = calendar.find((e) => e.event === event && e.codes.includes(code));
        if (!race)
          warnings.push({
            severity: "warning",
            code: "column-no-calendar",
            tab,
            found: event,
            headerCol: FIRST_EVENT_COL + table2.events.indexOf(event)
          });
        else {
          starts.push(race.start);
          dated.push(event);
        }
      }
      for (const race of calendar) {
        if (race.codes.includes(code) && !table2.events.includes(race.event)) {
          warnings.push({ severity: "warning", code: "calendar-no-column", row: race.row, tab, found: race.event });
        }
      }
      const expected = dated.map((event, i) => ({ event, start: starts[i], i })).sort((a, b) => a.start < b.start ? -1 : a.start > b.start ? 1 : a.i - b.i).map((e) => e.event);
      if (expected.join("\n") !== dated.join("\n")) {
        errors.push({ severity: "error", code: "column-order", tab, events: dated, expected });
        blockOf(code).all = true;
      }
    }
  }
  function place(map, key, name2, where, cell) {
    let names = map.get(key);
    if (!names) map.set(key, names = /* @__PURE__ */ new Map());
    const found = names.get(name2) ?? { places: [], cells: [] };
    if (!found.places.includes(where)) found.places.push(where);
    if (cell) found.cells.push(cell);
    names.set(name2, found);
  }
  var variantsOf = (names) => [...names].map(([name2, { places, cells }]) => ({ name: name2, places, cells }));
  function spellingChecks(tabs2, warnings) {
    const riders = /* @__PURE__ */ new Map();
    const classes = /* @__PURE__ */ new Map();
    const events = /* @__PURE__ */ new Map();
    const classesOf = /* @__PURE__ */ new Map();
    for (const { code, tab, table: table2 } of tabs2) {
      for (const event of table2.events) place(events, looseKey(event), event, code);
      for (const category of table2.categories) {
        place(classes, looseKey(category.name), category.name, code);
        for (const rider of category.riders) {
          const key = riderKey(rider.name);
          if (key === "") continue;
          place(riders, key, rider.name, code + " " + category.name, { code, tab, row: rider.row, col: NAME_COL });
          let set2 = classesOf.get(key);
          if (!set2) classesOf.set(key, set2 = /* @__PURE__ */ new Set());
          set2.add(code + "\n" + category.name);
        }
      }
    }
    for (const names of riders.values()) {
      if (names.size > 1) warnings.push({ severity: "warning", code: "name-spelling", variants: variantsOf(names) });
    }
    const keys = [...riders.keys()].sort(naturalOrder);
    for (let i = 0; i < keys.length; i++) {
      for (let j = i + 1; j < keys.length; j++) {
        const a = keys[i];
        const b = keys[j];
        if (!similar(a, b)) continue;
        const inA = [...classesOf.get(a)].map((s) => s.split("\n"));
        const inB = [...classesOf.get(b)].map((s) => s.split("\n"));
        const shared = inA.some(([codeA, clsA]) => inB.some(([codeB, clsB]) => clsA === clsB && codeA !== codeB));
        if (!shared) continue;
        warnings.push({
          severity: "warning",
          code: "name-similar",
          variants: [...variantsOf(riders.get(a)), ...variantsOf(riders.get(b))]
        });
      }
    }
    for (const names of classes.values()) {
      if (names.size > 1) warnings.push({ severity: "warning", code: "class-spelling", variants: variantsOf(names) });
    }
    for (const names of events.values()) {
      if (names.size > 1) warnings.push({ severity: "warning", code: "event-spelling", variants: variantsOf(names) });
    }
  }
  function similar(a, b) {
    if (a.length < 4 || b.length < 4) return false;
    return editDistance(a, b) <= 1 || b.startsWith(a + " ") || a.startsWith(b + " ");
  }
  function sharedRoundChecks(tabs2, calendar, errors, warnings, blockOf) {
    for (const race of calendar) {
      const inRace = tabs2.filter((t) => race.codes.includes(t.code) && t.table.events.includes(race.event));
      if (inRace.length < 2) continue;
      const classNames = [...new Set(inRace.flatMap((t) => t.table.categories.map((c) => c.name)))].sort(naturalOrder);
      for (const category of classNames) {
        const cells = inRace.flatMap((t) => {
          const cls = t.table.categories.find((c) => c.name === category);
          if (!cls) return [];
          const e = t.table.events.indexOf(race.event);
          const byKey = /* @__PURE__ */ new Map();
          for (const r of cls.riders) {
            const key = riderKey(r.name);
            if (key !== "") byKey.set(key, { name: r.name, text: r.texts[e], row: r.row, plate: r.plate });
          }
          return [{ code: t.code, tab: t.tab, col: FIRST_EVENT_COL + e, byKey }];
        });
        if (cells.length < 2) continue;
        sameCells(race.event, category, cells, errors, warnings, blockOf);
      }
    }
  }
  var isResult = (text) => text !== ABSENT && text !== NOT_HELD && text !== "";
  function sameCells(event, category, cells, errors, warnings, blockOf) {
    const keys = [...new Set(cells.flatMap((c) => [...c.byKey.keys()]))];
    let blocked = false;
    for (const key of keys) {
      const values = [];
      const missing = [];
      let rider = "";
      for (const { code, tab, col, byKey } of cells) {
        const cell = byKey.get(key);
        if (!cell) {
          missing.push({ code, tab, col });
          continue;
        }
        rider || (rider = cell.name);
        values.push({ code, tab, row: cell.row, col, plate: cell.plate, found: cell.text });
      }
      const filled = values.filter((v) => v.found !== "");
      const differ = new Set(filled.map((v) => v.found)).size > 1;
      const unlisted = missing.length > 0 && values.some((v) => isResult(v.found));
      if (differ) errors.push({ severity: "error", code: "shared-outcome", category, rider, event, values });
      if (unlisted) errors.push({ severity: "error", code: "shared-missing", category, rider, event, values, missing });
      blocked || (blocked = differ || unlisted);
      if (!differ && !unlisted && filled.length > 0 && filled.length < values.length)
        warnings.push({ severity: "warning", code: "shared-pending", category, rider, event, values });
    }
    if (blocked) for (const { code } of cells) blockOf(code).classes.add(category);
  }

  // plugins/csm-standings/src/engine/distances.ts
  var DISTANCES_TAB = "DISTANCES";
  var DISTANCES_HEADERS = ["FROM", "TO", "KM"];
  var KM_RE = /^\d+$/;
  var pairKey = (a, b) => a < b ? a + "\n" + b : b + "\n" + a;
  function stretchesOf(calendar) {
    const placed = calendar.filter((e) => e.place !== null);
    return placed.flatMap(
      (to, i) => i > 0 && placed[i - 1].place.code !== to.place.code ? [{ from: placed[i - 1], to }] : []
    );
  }
  var kmOf = (km, s) => km.get(pairKey(s.from.place.code, s.to.place.code)) ?? null;
  function readDistances(rows3, venues, calendar, galaSeasons, formatted = /* @__PURE__ */ new Set()) {
    const km = /* @__PURE__ */ new Map();
    const needed = galaSeasons.flatMap(
      (season) => stretchesOf(calendar.filter((e) => e.season === season)).map((s) => ({ season, ...s }))
    );
    if (rows3 === null)
      return {
        km,
        issues: needed.length ? [{ severity: "error", code: "distances-missing", seasons: [...new Set(needed.map((n) => n.season))] }] : null
      };
    const header2 = rows3[0] ?? [];
    const issues3 = [];
    for (const expected of DISTANCES_HEADERS)
      if (!header2.includes(expected)) issues3.push({ severity: "error", code: "distances-header", row: 1, expected });
    if (issues3.length) return { km, issues: issues3 };
    const col = (h) => header2.indexOf(h);
    const codes = venues.blocked ? null : new Set(venues.entries.map((v) => v.code));
    const firstRow = /* @__PURE__ */ new Map();
    const typed = /* @__PURE__ */ new Set();
    for (const [i, cells] of rows3.entries()) {
      if (i === 0 || cells.every((c) => c === "")) continue;
      const row2 = i + 1;
      const get = (h) => cells[col(h)] ?? "";
      const at2 = (h, issue) => {
        issues3.push({ row: row2, col: col(h), column: h, severity: "error", ...issue });
      };
      const [from, to, value] = [get("FROM"), get("TO"), get("KM")];
      let ok = true;
      for (const [h, code] of [
        ["FROM", from],
        ["TO", to]
      ]) {
        if (code === "") at2(h, { code: "cell-empty" });
        else if (codes !== null && !codes.has(code)) at2(h, { code: "distance-venue-unknown", found: code });
        else continue;
        ok = false;
      }
      if (ok && from === to) {
        at2("TO", { code: "distance-same", found: to });
        ok = false;
      }
      if (value === "") at2("KM", { code: "cell-empty" });
      else if (!KM_RE.test(value)) at2("KM", { code: "distance-invalid", found: value });
      if (!ok) continue;
      const key = pairKey(from, to);
      if (!KM_RE.test(value) || formatted.has(row2)) {
        typed.add(key);
        continue;
      }
      const first = firstRow.get(key);
      if (first !== void 0) {
        at2("FROM", { code: "distance-twice", found: from + " – " + to, firstRow: first });
        continue;
      }
      firstRow.set(key, row2);
      km.set(key, Number(value));
    }
    const told = /* @__PURE__ */ new Set();
    for (const n of needed) {
      const [from, to] = [n.from.place.code, n.to.place.code];
      const key = n.season + "\n" + pairKey(from, to);
      if (kmOf(km, n) !== null || typed.has(pairKey(from, to)) || told.has(key)) continue;
      told.add(key);
      issues3.push({ severity: "error", code: "distance-none", season: n.season, from, to });
    }
    return { km, issues: errorsFirst(issues3) };
  }

  // plugins/csm-standings/src/engine/gala.ts
  var GALA_TAB = "GALA";
  var GALA_HEADERS = ["SEASON", "CODE", "PUBLIC"];
  var GALA_CLASS_ORDER = "GALA CLASS ORDER";
  function isPublicDate(text) {
    const m = /^(\d{4}-\d{2}-\d{2})(?: (\d{2}):(\d{2}))?$/.exec(text);
    return m !== null && isIsoDate(m[1]) && (!m[2] || Number(m[2]) < 24 && Number(m[3]) < 60);
  }
  function readGala(rows3, seasons) {
    if (rows3 === null) return { entries: [], issues: [] };
    const header2 = rows3[0] ?? [];
    const issues3 = [];
    const cols = GALA_HEADERS.map((expected) => {
      const col = header2.indexOf(expected);
      if (col === -1) issues3.push({ severity: "error", code: "gala-header", row: 1, expected });
      return col;
    });
    if (issues3.length) return { entries: [], issues: issues3 };
    const [seasonCol, codeCol, publicCol] = cols;
    const classesCol = header2.indexOf(GALA_CLASS_ORDER);
    const entries = [];
    const firstRowOf = /* @__PURE__ */ new Map();
    rows3.forEach((cells, i) => {
      if (i === 0 || cells.every((c) => c === "")) return;
      const row2 = i + 1;
      const at2 = (col, severity, issue) => {
        issues3.push({ row: row2, col, column: header2[col], severity, ...issue });
      };
      const season = cells[seasonCol] ?? "";
      const firstRow = firstRowOf.get(season);
      if (season === "") at2(seasonCol, "error", { code: "cell-empty" });
      else if (seasons !== null && !seasons.includes(season))
        at2(seasonCol, "error", { code: "gala-season-unknown", found: season });
      else if (firstRow !== void 0) at2(seasonCol, "error", { code: "gala-season-twice", found: season, firstRow });
      else {
        firstRowOf.set(season, row2);
        if ((cells[codeCol] ?? "") === "") at2(codeCol, "warning", { code: "gala-code-empty" });
        const when = cells[publicCol] ?? "";
        if (!isPublicDate(when)) at2(publicCol, "error", { code: "gala-public-invalid", found: when });
        const list3 = classesCol === -1 ? [] : splitList(cells[classesCol] ?? "");
        if (classesCol !== -1 && !list3.length) at2(classesCol, "error", { code: "cell-empty" });
        entries.push({ row: row2, season, col: classesCol, classes: list3.length ? list3 : null });
      }
    });
    if (classesCol === -1 && entries.length)
      issues3.push({ severity: "error", code: "gala-classes-header", row: 1, expected: GALA_CLASS_ORDER });
    return { entries, issues: issues3 };
  }

  // plugins/csm-standings/src/engine/highlights.ts
  var HIGHLIGHTS_TAB = "HIGHLIGHTS";
  var HIGHLIGHT_CODES = [
    "TITLES",
    "WINS",
    "PODIUMS",
    "PERFECT",
    "TIGHT",
    "WIDE",
    "IRON",
    "CLASSES",
    "FIELD"
  ];
  var HIGHLIGHTS_HEADERS = ["SEASON", "CODE", "TYPE", "SHOW", "VALUE", "NAMES"];
  var TROPHY = "TROPHY";
  var COMPUTED = "COMPUTED";
  var CUSTOM = "CUSTOM";
  var HIGHLIGHT_TEXTS = ["TITLE", "UNIT", "DETAIL", "EXPLANATION"];
  var isComputed = (code) => HIGHLIGHT_CODES.includes(code);
  var NUMBER_RE = /^\d+$/;
  var NAMES_SEPARATOR = /\s*,\s*/;
  function readHighlights(rows3, seasons, galaSeasons, languages) {
    const lineups = /* @__PURE__ */ new Map();
    if (rows3 === null)
      return {
        lineups,
        issues: galaSeasons.length ? [{ severity: "error", code: "highlights-missing", seasons: [...galaSeasons] }] : null
      };
    const header2 = rows3[0] ?? [];
    const issues3 = [];
    const found = /* @__PURE__ */ new Map();
    const langs = /* @__PURE__ */ new Set();
    header2.forEach((h, col2) => {
      const m = /^(.+) ([A-Z]{2})$/.exec(h);
      if (!m || !HIGHLIGHT_TEXTS.includes(m[1])) return;
      const lang = m[2].toLowerCase();
      if (languages && !languages.includes(lang))
        issues3.push({ severity: "warning", row: 1, col: col2, column: h, code: "language-unknown", found: m[2], languages });
      else {
        found.set(h, col2);
        langs.add(lang);
      }
    });
    const required = languages ?? (langs.size ? [...langs] : ["en"]);
    const textHeaders = required.flatMap((lang) => HIGHLIGHT_TEXTS.map((t) => t + " " + lang.toUpperCase()));
    for (const expected of [...HIGHLIGHTS_HEADERS, ...textHeaders])
      if (!header2.includes(expected)) issues3.push({ severity: "error", code: "highlights-header", row: 1, expected });
    if (issues3.some((i) => i.severity === "error")) return { lineups, issues: errorsFirst(issues3) };
    const col = (h) => header2.indexOf(h);
    const off = /* @__PURE__ */ new Set();
    let allOff = false;
    for (const [i, cells] of rows3.entries()) {
      if (i === 0 || cells.every((c) => c === "")) continue;
      const row2 = i + 1;
      const get = (h) => cells[col(h)] ?? "";
      const at2 = (h, severity, issue) => {
        issues3.push({ row: row2, col: col(h), column: h, severity, ...issue });
      };
      const season = get("SEASON");
      if (season === "" || seasons !== null && !seasons.includes(season)) {
        at2(
          "SEASON",
          "error",
          season === "" ? { code: "cell-empty" } : { code: "highlights-season-unknown", found: season }
        );
        allOff = true;
        continue;
      }
      const lineup = lineups.get(season) ?? [];
      lineups.set(season, lineup);
      const errors = issues3.length;
      const code = get("CODE");
      const type = get("TYPE");
      const show = get("SHOW");
      if (code === "") at2("CODE", "error", { code: "cell-empty" });
      else {
        const first = lineup.find((r) => r.code === code);
        if (first) at2("CODE", "error", { code: "highlight-twice", found: code, firstRow: first.row });
      }
      if (type !== COMPUTED && type !== CUSTOM) at2("TYPE", "error", { code: "type-invalid", found: type });
      if (show !== "YES" && show !== "NO") at2("SHOW", "error", { code: "show-invalid", found: show });
      const trophy = get(TROPHY);
      if (trophy !== "" && trophy !== "YES" && trophy !== "NO")
        at2(TROPHY, "error", { code: "trophy-invalid", found: trophy });
      const content = ["VALUE", "NAMES", ...found.keys()];
      let manual = null;
      if (type === COMPUTED) {
        if (code !== "" && !isComputed(code)) at2("CODE", "error", { code: "highlight-unknown", found: code });
        for (const h of content) if (get(h) !== "") at2(h, "error", { code: "highlight-computed-content", found: code });
      } else if (type === CUSTOM) {
        if (isComputed(code)) at2("CODE", "error", { code: "highlight-reserved", found: code });
        const value = get("VALUE");
        if (value !== "" && !NUMBER_RE.test(value))
          at2("VALUE", "error", { code: "highlight-value-invalid", found: value });
        const units = textHeaders.filter((h) => h.startsWith("UNIT "));
        if (value === "") {
          for (const h of units) if (get(h) !== "") at2(h, "error", { code: "unit-without-value", found: get(h) });
        }
        const empty = ["NAMES", ...textHeaders].filter((h) => get(h) === "" && (value !== "" || !units.includes(h)));
        if (empty.length) issues3.push({ row: row2, severity: "error", code: "highlight-incomplete", found: code, empty });
        const texts = {};
        for (const lang of required)
          texts[lang] = Object.fromEntries(HIGHLIGHT_TEXTS.map((t) => [t, get(t + " " + lang.toUpperCase())]));
        manual = { value, names: get("NAMES").split(NAMES_SEPARATOR), texts };
      }
      if (issues3.length > errors) off.add(season);
      lineup.push({ row: row2, code, show: show === "YES", trophy: trophy === "YES", manual });
    }
    for (const season of galaSeasons) {
      const lineup = lineups.get(season);
      if (!lineup) {
        issues3.push({ severity: "error", code: "highlights-none", season });
        continue;
      }
      for (const code of HIGHLIGHT_CODES)
        if (!lineup.some((r) => r.code === code)) {
          issues3.push({ severity: "error", code: "highlight-missing", season, found: code });
          off.add(season);
        }
    }
    for (const season of lineups.keys()) if (allOff || off.has(season)) lineups.set(season, null);
    return { lineups, issues: errorsFirst(issues3) };
  }

  // plugins/csm-standings/src/engine/media-kit.ts
  var MEDIA_KIT_TAB = "MEDIA KIT";
  var MEDIA_KIT_HEADERS = ["SEASON", "NAME", "CODE"];
  function readMediaKit(rows3, seasons, formatted = /* @__PURE__ */ new Set()) {
    const lists = /* @__PURE__ */ new Map();
    if (rows3 === null) return { lists, nameCol: -1, issues: null };
    const header2 = rows3[0] ?? [];
    const issues3 = [];
    for (const expected of MEDIA_KIT_HEADERS)
      if (!header2.includes(expected)) issues3.push({ severity: "error", code: "media-kit-header", row: 1, expected });
    if (issues3.length) return { lists, nameCol: -1, issues: issues3 };
    const col = (h) => header2.indexOf(h);
    for (const [i, cells] of rows3.entries()) {
      if (i === 0 || cells.every((c) => c === "")) continue;
      const row2 = i + 1;
      const get = (h) => cells[col(h)] ?? "";
      const at2 = (h, issue, severity = "error") => {
        issues3.push({ row: row2, col: col(h), column: h, severity, ...issue });
      };
      const season = get("SEASON");
      const name2 = get("NAME");
      const code = get("CODE");
      if (season === "") at2("SEASON", { code: "cell-empty" });
      else if (seasons !== null && !seasons.includes(season))
        at2("SEASON", { code: "media-kit-season-unknown", found: season });
      if (name2 === "") at2("NAME", { code: "cell-empty" });
      if (season === "" || name2 === "" || seasons !== null && !seasons.includes(season) || formatted.has(row2)) continue;
      const list3 = lists.get(season) ?? [];
      const first = list3.find((r) => r.name === name2);
      if (first) {
        at2("NAME", { code: "media-kit-twice", found: name2, firstRow: first.row }, "warning");
        continue;
      }
      if (code === "") {
        at2("CODE", { code: "media-kit-code-empty", found: name2 }, "warning");
        continue;
      }
      const same = list3.find((r) => r.code === code);
      if (same)
        at2("CODE", { code: "media-kit-code-twice", found: name2, other: same.name, firstRow: same.row }, "warning");
      list3.push({ row: row2, name: name2, code });
      lists.set(season, list3);
    }
    return { lists, nameCol: col("NAME"), issues: errorsFirst(issues3) };
  }
  function mediaKitOf(list3, tables, season, nameCol) {
    const riders = new Set(tables.flatMap((t) => t.categories.flatMap((k) => k.riders.map((r) => r.name))));
    const names = /* @__PURE__ */ new Map();
    const issues3 = [];
    for (const r of list3) {
      if (riders.has(r.name)) names.set(r.name, r.code);
      else
        issues3.push({
          row: r.row,
          col: nameCol,
          column: "NAME",
          severity: "error",
          code: "media-kit-name-unknown",
          found: r.name,
          season
        });
    }
    return { names, issues: issues3 };
  }

  // plugins/csm-standings/src/engine/promotions.ts
  var PROMOTIONS_TAB = "PROMOTIONS";
  var PROMOTIONS_HEADERS = ["SEASON", "NAME", "DISCIPLINE", "FROM", "TO"];
  function readPromotions(rows3, seasons, levelsOf) {
    const lists = /* @__PURE__ */ new Map();
    const off = /* @__PURE__ */ new Set();
    if (rows3 === null) return { lists, off, allOff: false, nameCol: -1, issues: null };
    const header2 = rows3[0] ?? [];
    const issues3 = [];
    for (const expected of PROMOTIONS_HEADERS)
      if (!header2.includes(expected)) issues3.push({ severity: "error", code: "promotions-header", row: 1, expected });
    if (issues3.length) return { lists, off, allOff: true, nameCol: -1, issues: issues3 };
    const col = (h) => header2.indexOf(h);
    let allOff = false;
    for (const [i, cells] of rows3.entries()) {
      if (i === 0 || cells.every((c) => c === "")) continue;
      const row2 = i + 1;
      const get = (h) => cells[col(h)] ?? "";
      const at2 = (h, issue) => {
        issues3.push({ row: row2, col: col(h), column: h, severity: "error", ...issue });
      };
      const season = get("SEASON");
      if (season === "" || seasons !== null && !seasons.includes(season)) {
        at2("SEASON", season === "" ? { code: "cell-empty" } : { code: "promotions-season-unknown", found: season });
        allOff = true;
        continue;
      }
      const list3 = lists.get(season) ?? [];
      lists.set(season, list3);
      const errors = issues3.length;
      const name2 = get("NAME");
      const discipline = get("DISCIPLINE");
      const from = get("FROM");
      const to = get("TO");
      if (name2 === "") at2("NAME", { code: "cell-empty" });
      const ladders = levelsOf(season) ?? {};
      const ladder = Object.prototype.hasOwnProperty.call(ladders, discipline) ? ladders[discipline] : null;
      if (discipline === "") at2("DISCIPLINE", { code: "cell-empty" });
      else if (ladder === null)
        at2("DISCIPLINE", { code: "discipline-unknown", found: discipline, disciplines: Object.keys(ladders) });
      if (ladder !== null)
        for (const h of ["FROM", "TO"]) {
          const level = get(h);
          if (level === "") at2(h, { code: "cell-empty" });
          else if (!ladder.includes(level)) at2(h, { code: "level-invalid", found: level, discipline, levels: ladder });
        }
      if (ladder !== null && ladder.includes(from) && ladder.includes(to) && ladder.indexOf(to) <= ladder.indexOf(from))
        at2("TO", { code: "promotion-not-up", from, to });
      if (name2 !== "" && discipline !== "") {
        const first = list3.find((p) => p.name === name2 && p.discipline === discipline);
        if (first) at2("NAME", { code: "promotion-twice", found: name2, discipline, firstRow: first.row });
      }
      if (issues3.length > errors) off.add(season);
      if (name2 !== "") list3.push({ row: row2, name: name2, discipline, from, to });
    }
    return { lists, off, allOff, nameCol: col("NAME"), issues: errorsFirst(issues3) };
  }
  function promotionsOf(list3, tables, season, nameCol) {
    const issues3 = [];
    const at2 = (p, issue, severity) => {
      issues3.push({ row: p.row, col: nameCol, column: "NAME", severity, ...issue });
    };
    const promotions = list3.map((p) => {
      const rows3 = tables.flatMap(
        (t) => t.categories.flatMap((k) => k.riders.filter((r) => r.name === p.name).map((r) => ({ k: k.name, r })))
      );
      if (!rows3.length) at2(p, { code: "promotion-name-unknown", found: p.name, season }, "error");
      const own2 = rows3.filter((x) => x.k.split(" ")[0] === p.discipline);
      if (rows3.length && !own2.length)
        at2(p, { code: "promotion-no-discipline", found: p.name, discipline: p.discipline }, "warning");
      const plates = [];
      for (const x of own2.length ? own2 : rows3)
        if (x.r.plate !== "" && !plates.includes(x.r.plate)) plates.push(x.r.plate);
      return { ...p, plates };
    });
    return {
      promotions: issues3.some((i) => i.severity === "error") ? null : promotions,
      issues: errorsFirst(issues3)
    };
  }

  // plugins/csm-standings/src/engine/joker.ts
  function jokerConfigFor(championship2, className) {
    const joker = championship2.config.joker;
    if (joker?.enabled !== true) return null;
    return (joker.categories ?? []).includes(className) ? joker : null;
  }
  function jokerEvents(events, joker) {
    const eligible = joker.eligibleEvents ?? [];
    const protectedEvents = joker.protectedEvents ?? [];
    const out = [];
    events.forEach((name2, e) => {
      if (eligible.includes(name2) && !protectedEvents.includes(name2)) out.push(e);
    });
    return out;
  }
  function computeJoker(results2, events, overall, joker) {
    const numberPriority = RACED_WITHOUT_POINTS.length;
    let best2 = null;
    let candidates = 0;
    for (const e of jokerEvents(events, joker)) {
      const v = results2[e];
      const priority = typeof v === "number" ? numberPriority : RACED_WITHOUT_POINTS.indexOf(v);
      if (priority < 0) continue;
      candidates++;
      const points2 = typeof v === "number" ? v : 0;
      if (best2 === null || priority < best2.priority || priority === best2.priority && points2 < best2.points) {
        best2 = { event: e, priority, points: points2 };
      }
    }
    if (best2 === null || candidates === 1 && best2.priority === numberPriority) {
      return { label: "", points: 0, net: overall };
    }
    return { label: events[best2.event], points: best2.points, net: overall - best2.points };
  }

  // plugins/csm-standings/src/engine/ranking.ts
  function overallOf(results2) {
    let sum = 0;
    for (const v of results2) if (typeof v === "number") sum += v;
    return sum;
  }
  function heldEvents(riders, eventCount) {
    const events = [];
    for (let e = eventCount - 1; e >= 0; e--) {
      if (riders.some((results2) => results2[e] !== "" && results2[e] !== NOT_HELD)) events.push(e);
    }
    return events;
  }
  var NON_NUMERIC_RANK = /* @__PURE__ */ new Map([
    ["DNF", -1],
    ["DNS", -2],
    ["X", -3]
  ]);
  function strength(v) {
    if (typeof v === "number") return v;
    return NON_NUMERIC_RANK.get(v) ?? -4;
  }
  function rankRulebook(riders, totals, eventCount, scale) {
    const results2 = riders.map((r) => r.results);
    const events = heldEvents(results2, eventCount);
    const finals = events.filter((e) => !isCancelledRace(results2, e, scale));
    const profiles = riders.map((rider, i) => ({
      index: i,
      total: totals[i],
      places: placeCounts(rider.results, finals, scale),
      events: events.map((e) => strength(rider.results[e])),
      name: riderKey(rider.name)
    }));
    profiles.sort(compareRulebook);
    return profiles.map((p) => p.index);
  }
  function compareRulebook(a, b) {
    if (a.total !== b.total) return b.total - a.total;
    for (let i = 0; i < a.places.length; i++) {
      if (a.places[i] !== b.places[i]) return b.places[i] - a.places[i];
    }
    for (let j = 0; j < a.events.length; j++) {
      if (a.events[j] !== b.events[j]) return b.events[j] - a.events[j];
    }
    return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
  }
  function tiebreakOf(riders, eventCount, scale, a, b) {
    const events = heldEvents(riders, eventCount);
    const finals = events.filter((e) => !isCancelledRace(riders, e, scale));
    const pa = placeCounts(riders[a], finals, scale);
    const pb = placeCounts(riders[b], finals, scale);
    const i = pa.findIndex((n, j2) => n !== pb[j2]);
    if (i !== -1)
      return i < scale.length ? { kind: "place", place: i + 1, a: pa[i], b: pb[i] } : { kind: "outside", a: pa[i], b: pb[i] };
    const j = events.findIndex((e) => strength(riders[a][e]) !== strength(riders[b][e]));
    if (j !== -1) return { kind: "event", event: events[j], last: j === 0 };
    return { kind: "name" };
  }
  function placeCounts(results2, events, scale) {
    const counts = new Array(scale.length + 1).fill(0);
    for (const e of events) {
      const v = results2[e];
      if (typeof v !== "number") continue;
      const place2 = v === 0 ? scale.length : scale.indexOf(v);
      if (place2 !== -1) counts[place2]++;
    }
    return counts;
  }
  function isCancelledRace(riders, event, scale) {
    const points2 = riders.map((results2) => results2[event]).filter((v) => typeof v === "number");
    return points2.length > 1 && points2.every((v) => v === scale[0]);
  }

  // plugins/csm-standings/src/engine/points.ts
  function checkPoints(table2, scale, skip) {
    const issues3 = [];
    for (const category of table2.categories) {
      if (skip.has(category.name)) continue;
      table2.events.forEach((column, e) => {
        checkEvent(category, e, column, scale, issues3);
      });
    }
    return issues3;
  }
  function checkEvent(category, e, column, scale, issues3) {
    const top2 = scale[0];
    const last = scale[scale.length - 1];
    const col = FIRST_EVENT_COL + e;
    const where = { col, column, category: category.name };
    const results2 = category.riders.map((r) => r.results);
    if (isCancelledRace(results2, e, scale)) {
      for (const r of category.riders) {
        const v = r.results[e];
        if (v === top2 || v === ABSENT) continue;
        issues3.push({
          ...where,
          severity: "error",
          code: "cancelled-race-value",
          row: r.row,
          rider: r.name,
          found: r.texts[e],
          top: top2
        });
      }
      return;
    }
    const points2 = [];
    for (const r of category.riders) {
      const v = r.results[e];
      if (typeof v !== "number") continue;
      if (Object.is(v, -0) || v !== 0 && !scale.includes(v)) {
        issues3.push({
          ...where,
          severity: "error",
          code: "points-off-scale",
          row: r.row,
          rider: r.name,
          found: r.texts[e],
          scale
        });
        continue;
      }
      points2.push(v);
    }
    const winners = category.riders.filter((r) => r.results[e] === top2);
    if (winners.length > 1) {
      issues3.push({
        ...where,
        severity: "error",
        code: "top-twice",
        top: top2,
        riders: winners.map((r) => ({ row: r.row, name: r.name }))
      });
    }
    const paid = points2.filter((v) => v > 0);
    const twice = paid.filter((v, i) => v !== top2 && paid.indexOf(v) !== i).filter((v, i, list3) => list3.indexOf(v) === i);
    const lowest = Math.min(...paid);
    const holes = scale.filter((v) => v > lowest && !paid.includes(v));
    if (twice.length) issues3.push({ ...where, severity: "warning", code: "points-twice", points: twice });
    if (holes.length) issues3.push({ ...where, severity: "warning", code: "points-missing", points: holes });
    if (points2.includes(0) && !paid.includes(last)) {
      issues3.push({ ...where, severity: "warning", code: "zero-while-last-free", last });
    }
  }

  // plugins/csm-standings/src/engine/table.ts
  var NUMBER = /^-?\d+(\.\d+)?$/;
  function resultOf(text) {
    return NUMBER.test(text) ? Number(text) : text;
  }
  function readTable(values) {
    const table2 = { events: [], categories: [], issues: [] };
    const header2 = values[0] ?? [];
    const width = lastFilledIndex(header2) + 1;
    if (width === 0) {
      table2.issues.push({ severity: "error", code: "empty-sheet", row: 1 });
      return table2;
    }
    checkHeader(header2, width, table2.issues);
    if (table2.issues.length) return table2;
    table2.events = header2.slice(FIRST_EVENT_COL, width);
    const byName2 = /* @__PURE__ */ new Map();
    values.forEach((cells, i) => {
      if (i === 0 || lastFilledIndex(cells) === -1) return;
      const rider = readRider(cells, i + 1, table2, width);
      const name2 = cells[CATEGORY_COL];
      if (name2 === "") return;
      let category = byName2.get(name2);
      if (!category) byName2.set(name2, category = { name: name2, riders: [] });
      category.riders.push(rider);
    });
    table2.categories = [...byName2.values()].sort((a, b) => naturalOrder(a.name, b.name));
    for (const category of table2.categories) checkCategory(category, table2);
    checkEntry(table2);
    return table2;
  }
  var hasResult = (category, e) => category.riders.some((r) => r.texts[e] !== "" && r.texts[e] !== NOT_HELD);
  function eventsEntered(table2) {
    return table2.events.map(
      (_, e) => table2.categories.length > 0 && table2.categories.every((k) => hasResult(k, e) || k.riders.every((r) => r.texts[e] === NOT_HELD))
    );
  }
  function checkEntry(table2) {
    table2.events.forEach((column, e) => {
      const entered = table2.categories.filter((k) => hasResult(k, e)).map((k) => k.name);
      const pending = table2.categories.filter((k) => k.riders.every((r) => r.texts[e] === "")).map((k) => k.name);
      if (entered.length && pending.length) {
        table2.issues.push({
          severity: "warning",
          code: "event-partial",
          col: FIRST_EVENT_COL + e,
          column,
          entered,
          pending
        });
      }
    });
  }
  function lastFilledIndex(cells) {
    for (let i = cells.length - 1; i >= 0; i--) if (cells[i] !== "") return i;
    return -1;
  }
  function checkHeader(header2, width, issues3) {
    const at2 = (col) => ({ severity: "error", row: 1, col });
    FIXED_LABELS.forEach((expected, col) => {
      const found = header2[col] ?? "";
      if (found !== expected) issues3.push({ ...at2(col), code: "header-label", expected, found });
    });
    if (width <= FIRST_EVENT_COL) {
      issues3.push({ ...at2(FIRST_EVENT_COL), code: "no-events" });
      return;
    }
    const seen = /* @__PURE__ */ new Map();
    for (let col = FIRST_EVENT_COL; col < width; col++) {
      const found = header2[col];
      const firstCol = seen.get(found);
      if (found === "") issues3.push({ ...at2(col), code: "event-empty" });
      else if (hasExtraSpaces(found)) issues3.push({ ...at2(col), code: "spaces", found });
      else if (RESERVED_EVENT_NAMES.includes(found.toUpperCase()))
        issues3.push({ ...at2(col), code: "event-reserved", found });
      else if (firstCol !== void 0) issues3.push({ ...at2(col), code: "event-twice", found, firstCol });
      else seen.set(found, col);
    }
  }
  function readRider(cells, row2, table2, width) {
    const category = cells[CATEGORY_COL];
    const plate3 = cells[PLATE_COL] ?? "";
    const name2 = cells[NAME_COL] ?? "";
    const where = { severity: "error", row: row2, ...category === "" ? {} : { category }, rider: name2 };
    const cell = (col) => ({ ...where, col, column: col < width ? table2.events[col - FIRST_EVENT_COL] : "" });
    const issues3 = table2.issues;
    if (category === "")
      issues3.push({ ...where, col: CATEGORY_COL, column: FIXED_LABELS[CATEGORY_COL], code: "category-empty" });
    if (name2 === "") issues3.push({ ...where, col: NAME_COL, column: FIXED_LABELS[NAME_COL], code: "name-empty" });
    else if (name2 !== name2.toUpperCase()) {
      issues3.push({ ...where, col: NAME_COL, column: FIXED_LABELS[NAME_COL], code: "name-case", found: name2 });
    }
    for (const col of [CATEGORY_COL, PLATE_COL, NAME_COL]) {
      const found = cells[col] ?? "";
      if (hasExtraSpaces(found)) issues3.push({ ...where, col, column: FIXED_LABELS[col], code: "spaces", found });
    }
    const texts = [];
    for (let col = FIRST_EVENT_COL; col < width; col++) {
      const found = cells[col] ?? "";
      texts.push(found);
      if (hasExtraSpaces(found)) issues3.push({ ...cell(col), code: "spaces", found });
      else if (found !== "" && !NUMBER.test(found) && !SPECIAL_VALUES.includes(found)) {
        issues3.push({ ...cell(col), code: "value-invalid", found });
      }
    }
    for (let col = width; col < cells.length; col++) {
      const found = cells[col];
      if (found !== "") issues3.push({ ...cell(col), code: "outside-table", found });
    }
    return { row: row2, plate: plate3, name: name2, texts, results: texts.map(resultOf) };
  }
  function checkCategory(category, table2) {
    const issues3 = table2.issues;
    const { name: name2, riders } = category;
    const seen = /* @__PURE__ */ new Map();
    for (const rider of riders) {
      const key = riderKey(rider.name);
      if (key === "") continue;
      const firstRow = seen.get(key);
      if (firstRow === void 0) seen.set(key, rider.row);
      else {
        issues3.push({
          severity: "error",
          code: "rider-twice",
          row: rider.row,
          col: NAME_COL,
          column: FIXED_LABELS[NAME_COL],
          category: name2,
          rider: rider.name,
          found: rider.name,
          firstRow
        });
      }
    }
    table2.events.forEach((column, e) => {
      const where = { severity: "error", col: FIRST_EVENT_COL + e, column, category: name2 };
      const others = riders.filter((r) => r.texts[e] !== NOT_HELD);
      if (others.length && others.length < riders.length) {
        issues3.push({ ...where, code: "na-mixed", cells: others.map((r) => ({ row: r.row, found: r.texts[e] })) });
        return;
      }
      const blanks = riders.filter((r) => r.texts[e] === "");
      if (blanks.length && blanks.length < riders.length) {
        issues3.push({ ...where, code: "blank-mixed", rows: blanks.map((r) => r.row) });
      }
    });
  }

  // plugins/csm-standings/src/engine/standings.ts
  var NOTHING_BLOCKED = { all: false, classes: /* @__PURE__ */ new Set() };
  function standingsOfTable(table2, championship2, outer = NOTHING_BLOCKED, extraIssues = []) {
    const issues3 = [...table2.issues, ...extraIssues];
    issues3.push(...checkPoints(table2, pointsScaleOf(championship2), blockedCategories(issues3)));
    issues3.push(...configWarnings(championship2, table2));
    const blocked = outer.all || blocksSheet(issues3);
    const blockedClasses = blockedCategories(issues3);
    const classes = table2.categories.map(
      (category) => blocked || blockedClasses.has(category.name) || outer.classes.has(category.name) ? { name: category.name, blocked: true, headers: [], riders: [] } : rankClass(category, table2.events, championship2)
    );
    return { blocked, classes, issues: errorsFirst(issues3) };
  }
  function rankOnEvents(className, riders, events, count2, championship2) {
    const joker = jokerConfigFor(championship2, className);
    const totals = riders.map((rider) => {
      const results2 = rider.results.slice(0, count2);
      const overall = overallOf(results2);
      return { overall, joker: joker ? computeJoker(results2, events.slice(0, count2), overall, joker) : null };
    });
    const order = rankRulebook(
      riders,
      totals.map((t) => t.joker?.net ?? t.overall),
      count2,
      pointsScaleOf(championship2)
    );
    return { totals, order };
  }
  function rankClass(category, events, championship2) {
    const { totals, order } = rankOnEvents(category.name, category.riders, events, events.length, championship2);
    const withJoker = jokerConfigFor(championship2, category.name) !== null;
    const headers = [COLUMN_RANK, COLUMN_PLATE, COLUMN_NAME, ...events, COLUMN_TOTAL];
    if (withJoker) headers.push(COLUMN_JOKER, COLUMN_NET_TOTAL);
    const rows3 = category.riders.map((rider, i) => {
      const row2 = { [COLUMN_PLATE]: rider.plate, [COLUMN_NAME]: rider.name };
      events.forEach((event, e) => {
        row2[event] = rider.texts[e];
      });
      const { overall, joker } = totals[i];
      row2[COLUMN_TOTAL] = String(overall);
      if (joker) {
        row2[COLUMN_JOKER] = joker.label;
        row2[COLUMN_NET_TOTAL] = String(joker.net);
      }
      return row2;
    });
    const riders = order.map((i, position) => ({ [COLUMN_RANK]: String(position + 1), ...rows3[i] }));
    return { name: category.name, blocked: false, headers, riders };
  }

  // plugins/csm-standings/src/engine/workbook.ts
  var formatIssue = (f, category) => ({
    severity: "error",
    code: "number-format",
    row: f.row,
    col: f.col,
    ...category === void 0 ? {} : { category },
    found: f.found,
    format: f.format
  });
  var formattedRows = (payload, tab) => new Set(payload.formats.filter((f) => f.tab === tab).map((f) => f.row));
  function workbookOf(payload, site) {
    const readS = readSeasons(payload.seasons, site?.languages);
    const seasonsTab = site ? { ...readS, entries: readS.entries.map((e) => seasonIn(e, site.lang)) } : readS;
    const venues = readVenues(payload.venues, site?.languages);
    const seasons = seasonsOf(seasonsTab);
    const galaRows = payload.gala ?? null;
    const gala = readGala(galaRows, seasonsTab.blocked ? null : seasons);
    const read = readConfig(
      payload.config,
      payload.tabNames,
      seasonsTab,
      formattedRows(payload, CONFIG_TAB),
      site?.languages,
      new Set(gala.entries.map((e) => e.season))
    );
    const readRaces = readCalendar(
      payload.calendar,
      read,
      seasonsTab,
      venues,
      formattedRows(payload, CALENDAR_TAB),
      site?.languages
    );
    const config = site ? { ...read, entries: read.entries.map((e) => entryIn(e, site.lang)) } : read;
    const calendar = site ? { ...readRaces, events: readRaces.events.map((e) => eventIn(e, site.lang)) } : readRaces;
    const of = (tab) => payload.formats.filter((f) => f.tab === tab).map((f) => formatIssue(f));
    const highlights = readHighlights(
      payload.highlights ?? null,
      seasonsTab.blocked ? null : seasons,
      gala.entries.map((e) => e.season),
      site?.languages
    );
    const formatted = of(HIGHLIGHTS_TAB);
    for (const f of formatted) {
      const season = [...highlights.lineups].find(([, l]) => l?.some((r) => r.row === f.row));
      if (season) highlights.lineups.set(season[0], null);
    }
    const sponsors = readSponsors(
      payload.sponsors ?? null,
      seasonsTab.blocked ? null : seasons,
      gala.entries.map((e) => e.season),
      Object.keys(payload.sponsorLogos ?? {})
    );
    const sponsorFormats = of(SPONSORS_TAB);
    for (const [season, list3] of sponsors.lists) {
      const left = list3.filter((s) => !sponsorFormats.some((f) => f.row === s.row));
      if (left.length) sponsors.lists.set(season, left);
      else sponsors.lists.delete(season);
    }
    const distances = readDistances(
      payload.distances ?? null,
      venues,
      readRaces.events,
      gala.entries.map((e) => e.season),
      formattedRows(payload, DISTANCES_TAB)
    );
    const distanceFormats = of(DISTANCES_TAB);
    const promotions = promotionsChecked(payload, seasonsTab, seasons, config);
    const mediaKit = mediaKitChecked(payload, seasonsTab, seasons, config);
    return {
      blocked: seasonsTab.blocked || config.blocked || calendar.blocked,
      seasonsTab,
      venues,
      config,
      calendar,
      seasons,
      issues: errorsFirst([
        ...seasonsTab.issues,
        ...venues.issues,
        ...config.issues,
        ...calendar.issues,
        ...of(SEASONS_TAB),
        ...of(VENUES_TAB),
        ...of(CONFIG_TAB),
        ...of(CALENDAR_TAB)
      ]),
      gala: galaRows === null ? null : errorsFirst([...gala.issues, ...galaClassIssues(gala.entries, config, payload), ...of(GALA_TAB)]),
      galaEntries: gala.entries,
      highlights: highlights.issues === null && !formatted.length ? null : errorsFirst([...highlights.issues ?? [], ...formatted]),
      highlightLineups: highlights.lineups,
      sponsors: sponsors.issues === null && !sponsorFormats.length ? null : errorsFirst([...sponsors.issues ?? [], ...sponsorFormats]),
      sponsorLists: sponsors.lists,
      distances: distances.issues === null && !distanceFormats.length ? null : errorsFirst([...distances.issues ?? [], ...distanceFormats]),
      distanceKm: distances.km,
      promotions: promotions.issues,
      promotionLists: promotions.lists,
      promotionsAllOff: promotions.allOff,
      mediaKit: mediaKit.issues,
      mediaKitLists: mediaKit.lists
    };
  }
  function promotionsChecked(payload, seasonsTab, seasons, config) {
    const read = readPromotions(payload.promotions ?? null, seasonsTab.blocked ? null : seasons, (season) => {
      const rulebook = seasonsTab.entries.find((e) => e.season === season)?.rulebook ?? "";
      return isEdition(rulebook) ? RULEBOOKS[rulebook].levels ?? null : null;
    });
    const formats = payload.formats.filter((f) => f.tab === PROMOTIONS_TAB).map((f) => formatIssue(f));
    const lists = /* @__PURE__ */ new Map();
    const issues3 = [...read.issues ?? [], ...formats];
    for (const [season, rows3] of read.lists) {
      const tabs2 = config.entries.filter((e) => e.season === season && payload.tabNames.includes(e.tab)).map((e) => e.tab);
      if (!tabs2.every((tab) => tab in payload.tabs)) continue;
      const checked = promotionsOf(
        rows3,
        tabs2.map((tab) => readTable(payload.tabs[tab])),
        season,
        read.nameCol
      );
      issues3.push(...checked.issues);
      lists.set(season, read.allOff || read.off.has(season) || formats.length ? null : checked.promotions);
    }
    return {
      issues: read.issues === null && !formats.length ? null : errorsFirst(issues3),
      lists,
      allOff: read.allOff || formats.length > 0
    };
  }
  function mediaKitChecked(payload, seasonsTab, seasons, config) {
    const read = readMediaKit(
      payload.mediaKit ?? null,
      seasonsTab.blocked ? null : seasons,
      formattedRows(payload, MEDIA_KIT_TAB)
    );
    const formats = payload.formats.filter((f) => f.tab === MEDIA_KIT_TAB).map((f) => formatIssue(f));
    const issues3 = [...read.issues ?? [], ...formats];
    const lists = /* @__PURE__ */ new Map();
    for (const [season, rows3] of seasonsTab.blocked ? [] : read.lists) {
      const tabs2 = config.entries.filter((e) => e.season === season && payload.tabNames.includes(e.tab)).map((e) => e.tab);
      if (!tabs2.every((tab) => tab in payload.tabs)) continue;
      const checked = mediaKitOf(
        rows3,
        tabs2.map((tab) => readTable(payload.tabs[tab])),
        season,
        read.nameCol
      );
      issues3.push(...checked.issues);
      lists.set(season, checked.names);
    }
    return { issues: read.issues === null && !formats.length ? null : errorsFirst(issues3), lists };
  }
  var byOrder = (a, b) => a.order - b.order || a.row - b.row;
  function inListOrder(items, list3) {
    if (!list3.length) return { items: [...items], twice: [], missing: [] };
    const twice = [];
    const rank = /* @__PURE__ */ new Map();
    for (const found of list3) {
      if (rank.has(found)) twice.push(found);
      else if (items.some((k) => k.name === found)) rank.set(found, rank.size);
    }
    const missing = items.filter((k) => !rank.has(k.name)).map((k) => k.name);
    const at2 = (k) => rank.get(k.name) ?? rank.size;
    return { items: [...items].sort((a, b) => at2(a) - at2(b)), twice, missing };
  }
  function orderClasses(classes, entry) {
    const configRow = entry.row;
    const { items, twice, missing } = inListOrder(classes, classes.length ? entry.classes : []);
    return {
      classes: items,
      issues: [
        ...twice.map((found) => ({ severity: "warning", code: "classes-twice", found, configRow })),
        ...missing.map((found) => ({ severity: "warning", code: "classes-missing", found, configRow }))
      ]
    };
  }
  function galaClassIssues(entries, config, payload) {
    return entries.flatMap((g) => {
      if (g.classes === null) return [];
      const names = new Set(
        config.entries.filter((e) => e.season === g.season && e.tab in payload.tabs).flatMap((e) => readTable(payload.tabs[e.tab]).categories.map((k) => k.name))
      );
      const { twice, missing } = inListOrder(
        [...names].map((name2) => ({ name: name2 })),
        g.classes
      );
      const at2 = { severity: "warning", row: g.row, col: g.col, column: GALA_CLASS_ORDER };
      return [
        ...twice.map((found) => ({ ...at2, code: "gala-classes-twice", found })),
        ...missing.map((found) => ({ ...at2, code: "gala-classes-missing", found }))
      ];
    });
  }
  function galaOrderOf(workbook, season, entries) {
    const row2 = workbook.galaEntries.find((g) => g.season === season);
    if (!row2) return { champs: null, classes: null };
    const champs = entries.every((e) => e.galaOrder !== null) ? [...entries].sort((a, b) => a.galaOrder - b.galaOrder).map((e) => e.code) : null;
    return { champs, classes: row2.classes };
  }
  function seasonOf(workbook, payload, season) {
    const rules = workbook.seasonsTab.entries.find((e) => e.season === season);
    const entries = workbook.config.entries.filter((e) => e.season === season).sort(byOrder);
    const calendar = workbook.calendar.events.filter((e) => e.season === season);
    const readable = entries.flatMap((entry) => {
      const rows3 = payload.tabs[entry.tab];
      return entry.blocked || entry.championship === null || rows3 === void 0 ? [] : [{ entry, code: entry.code, tab: entry.tab, table: readTable(rows3) }];
    });
    const cross = crossChecks(readable, calendar);
    const none = { all: false, classes: /* @__PURE__ */ new Set() };
    const championships = entries.map((entry) => {
      const found = readable.find((r) => r.entry === entry);
      if (!found) return { entry, standings: null, blocked: true, events: [], entered: [] };
      const rows3 = payload.tabs[entry.tab];
      const categoryOf = (row2) => row2 > 1 ? rows3[row2 - 1][0] : "";
      const formats = payload.formats.filter((f) => f.tab === entry.tab).map((f) => formatIssue(f, categoryOf(f.row) || void 0));
      const standings = standingsOfTable(
        found.table,
        entry.championship,
        cross.blocks.get(entry.code) ?? none,
        formats
      );
      const ordered = orderClasses(standings.classes, entry);
      standings.classes = ordered.classes;
      standings.issues.push(...ordered.issues);
      return {
        entry,
        standings,
        blocked: standings.blocked,
        events: found.table.events,
        entered: eventsEntered(found.table)
      };
    });
    return {
      season,
      rules,
      championships,
      gala: galaOrderOf(workbook, season, entries),
      highlights: workbook.highlightLineups.get(season) ?? null,
      sponsors: (workbook.sponsorLists.get(season) ?? []).map((s) => ({
        ...s,
        image: s.logo === null ? null : payload.sponsorLogos[s.logo]
      })),
      promotions: workbook.promotionLists.has(season) ? workbook.promotionLists.get(season) ?? null : workbook.promotionsAllOff ? null : [],
      mediaKit: workbook.mediaKitLists.get(season) ?? /* @__PURE__ */ new Map(),
      calendar,
      distances: workbook.distanceKm,
      crossIssues: cross.issues
    };
  }

  // plugins/csm-standings/src/site/check-model.ts
  var isShared = (issue) => issue.code === "shared-outcome" || issue.code === "shared-missing" || issue.code === "shared-pending";
  var isResult2 = (text) => text !== ABSENT && text !== NOT_HELD && text !== "";
  var codesWithResult = (issue) => issue.values.filter((v) => isResult2(v.found)).map((v) => v.code);
  function complementary(a, b) {
    const fits = (x, y) => x.missing.every((m) => codesWithResult(y).includes(m.code));
    const same = (f) => a.values.some((v) => b.values.some((w) => f(v) !== "" && f(v) === f(w)));
    return a.event === b.event && a.category === b.category && riderKey(a.rider) !== riderKey(b.rider) && fits(a, b) && fits(b, a) && (same((v) => v.plate) || same((v) => v.found));
  }
  function groupIssues(issues3) {
    const shared = issues3.filter(isShared);
    const pairOf = /* @__PURE__ */ new Map();
    const pairs = /* @__PURE__ */ new Map();
    const missing = shared.filter((i) => i.code === "shared-missing");
    for (const a of missing) {
      const b = missing.find((m) => !pairOf.has(a) && !pairOf.has(m) && complementary(a, m));
      if (!b) continue;
      const key = [a.category, ...[a.rider, b.rider].map(riderKey).sort()].join("\n");
      pairOf.set(a, key).set(b, key);
      pairs.set(key, [...pairs.get(key) ?? [], a, b]);
    }
    const byRace = /* @__PURE__ */ new Map();
    const raceOf = (i) => (i.code === "shared-pending" ? "w" : "e") + i.event + "\n" + i.category;
    for (const issue of shared) {
      if (pairOf.has(issue)) continue;
      byRace.set(raceOf(issue), [...byRace.get(raceOf(issue)) ?? [], issue]);
    }
    const groups = [];
    const done = /* @__PURE__ */ new Set();
    for (const issue of issues3) {
      if (!isShared(issue)) {
        groups.push({ kind: "one", severity: issue.severity, issue });
        continue;
      }
      const key = pairOf.get(issue) ?? raceOf(issue);
      if (done.has(key)) continue;
      done.add(key);
      const pair = pairs.get(key);
      const race = byRace.get(key);
      if (pair) groups.push({ kind: "names", severity: "error", category: issue.category, issues: pair });
      else if (race.length === 1) groups.push({ kind: "one", severity: issue.severity, issue });
      else if (issue.code === "shared-pending")
        groups.push({ kind: "pending", severity: "warning", event: issue.event, category: issue.category, issues: race });
      else {
        const cause = causeOf(race);
        groups.push({
          kind: "column",
          severity: "error",
          event: issue.event,
          category: issue.category,
          issues: cause ? [cause, ...race.filter((i) => i !== cause)] : race,
          cause,
          sides: sidesOf(race)
        });
      }
    }
    return groups;
  }
  function valueIn(issue, code) {
    return issue.values.find((v) => v.code === code) ?? null;
  }
  var codesOf = (issue) => [
    ...issue.values.map((v) => v.code),
    ...issue.code === "shared-missing" ? issue.missing.map((m) => m.code) : []
  ];
  function causeOf(issues3) {
    const none = (i, code) => (valueIn(i, code)?.found ?? ABSENT) === ABSENT;
    return issues3.find((i) => codesWithResult(i).length > 0 && codesOf(i).some((code) => none(i, code))) ?? null;
  }
  function sidesOf(issues3) {
    const sides = /* @__PURE__ */ new Map();
    for (const code of new Set(issues3.flatMap(codesOf))) {
      const sign = issues3.map((i) => valueIn(i, code)?.found ?? "\0").join("\n");
      sides.set(sign, [...sides.get(sign) ?? [], code]);
    }
    return [...sides.values()].sort((a, b) => b.length - a.length);
  }
  function oddOnes(issue) {
    if (issue.code === "shared-pending") return new Set(issue.values.filter((v) => v.found === "").map((v) => v.code));
    const counts = /* @__PURE__ */ new Map();
    for (const v of issue.values) counts.set(v.found, (counts.get(v.found) ?? 0) + 1);
    const top2 = Math.max(...counts.values());
    const tops = [...counts].filter(([, n]) => n === top2);
    const common = tops.length === 1 && top2 > 1 ? tops[0][0] : null;
    const odd = issue.values.filter((v) => v.found !== common).map((v) => v.code);
    return /* @__PURE__ */ new Set([...odd, ...issue.code === "shared-missing" ? issue.missing.map((m) => m.code) : []]);
  }

  // plugins/csm-standings/src/site/i18n/en.ts
  var plural = (n, one, many) => String(n) + " " + (n === 1 ? one : many);
  var SPOKEN_EN = {
    names: {
      fr: "French",
      en: "English",
      es: "Spanish",
      it: "Italian",
      pt: "Portuguese",
      de: "German",
      ar: "Arabic",
      zh: "Mandarin"
    },
    levels: { native: "native", fluent: "fluent", intermediate: "intermediate", basic: "basic" }
  };
  var vs = (a, b) => String(a) + " vs " + String(b);
  var andList = (xs) => xs.length < 2 ? xs.join("") : xs.slice(0, -1).join(", ") + " and " + xs[xs.length - 1];
  function firstGoal(f, next) {
    const b = f.best;
    if (!b) return "Aim for [a podium / the title] in " + (f.classes[0] ?? "[class]") + " in " + next;
    const where = b.cls + " (" + b.code + ") in " + next;
    if (b.rank === 1 && b.final) return "Defend the title in " + where;
    return (b.rank <= 3 ? "Aim for the title in " : "Aim for a podium in ") + where;
  }
  var points = (n) => plural(n, "pt", "pts");
  var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
  var MONTHS_LONG = [
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December"
  ];
  var num = (n) => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  function dateRange(e) {
    const [, m1, d1] = e.start.split("-").map(Number);
    const [, m2, d2] = e.end.split("-").map(Number);
    if (e.start === e.end) return MONTHS[m1 - 1] + " " + String(d1);
    if (m1 === m2) return MONTHS[m1 - 1] + " " + String(d1) + "–" + String(d2);
    return MONTHS[m1 - 1] + " " + String(d1) + " – " + MONTHS[m2 - 1] + " " + String(d2);
  }
  function raceDays(days2) {
    const parts = days2.map((d) => d.split("-").map(Number));
    const oneMonth = parts.every(([, m]) => m === parts[0][1]);
    const texts = parts.map(([, m, d], i) => oneMonth && i > 0 ? String(d) : MONTHS[m - 1] + " " + String(d));
    const last = texts.pop();
    return texts.length ? texts.join(", ") + " and " + last : last;
  }
  function suffix(n) {
    const teen = n % 100 >= 11 && n % 100 <= 13;
    return teen ? "th" : ["th", "st", "nd", "rd"][n % 10] ?? "th";
  }
  var quote = (s) => "“" + s + "”";
  var list = (values) => values.join(", ");
  var RACE_DAYS_FALLBACK = " (START–END) under its results.";
  var CLASSES_PLACE = (row2) => "CONFIG, row " + String(row2) + ", column SITE CLASS ORDER: ";
  var rows = (n) => (n.length > 1 ? "at rows " : "at row ") + list(n);
  function variants(list3) {
    return list3.map((v) => quote(v.name) + " (" + v.places.join(", ") + ")").join(" / ");
  }
  function issuePlace(issue) {
    const parts = [];
    if (issue.row !== void 0) {
      const who2 = [issue.category, issue.rider].filter((s) => s !== void 0 && s !== "");
      parts.push("Row " + String(issue.row) + (who2.length ? " (" + who2.join(", ") + ")" : ""));
    } else if (issue.category !== void 0) {
      parts.push([issue.category, issue.rider].filter((s) => s !== void 0 && s !== "").join(", "));
    }
    if (issue.col !== void 0) {
      parts.push("column " + colLetter(issue.col) + (issue.column ? " (" + issue.column + ")" : ""));
    }
    return parts.length ? parts.join(", ") + ": " : "";
  }
  var issues = {
    "empty-sheet": () => "the sheet is empty. Row 1 must hold the header: CATEGORY, #, NAME, then the events.",
    "header-label": (issue) => "must be " + quote(issue.expected) + " (found " + quote(issue.found) + ").",
    "no-events": () => "the header must hold at least one event after NAME.",
    "event-empty": () => "the event's name is empty.",
    "event-reserved": (issue) => quote(issue.found) + " can't be an event name.",
    "event-twice": (issue) => "the event " + quote(issue.found) + " is already in column " + colLetter(issue.firstCol) + ". Each event must have its own name.",
    spaces: (issue) => quote(issue.found) + " has extra spaces (at the start, at the end or two in a row).",
    "outside-table": (issue) => quote(issue.found) + " is right of the last event: nothing is allowed right of the header.",
    "category-empty": () => "the class is empty.",
    "name-empty": () => "the rider's name is empty.",
    "name-case": (issue) => quote(issue.found) + " must be written in capitals.",
    "value-invalid": (issue) => quote(issue.found) + " isn't allowed. Allowed values: the points, DNF, DNS, X (absent), N/A (event not held) or blank (not raced yet), in capitals.",
    "na-mixed": (issue) => "N/A for some riders, but not " + rows(issue.cells.map((c) => c.row)) + " (" + list(issue.cells.map((c) => quote(c.found))) + "). If the event wasn't held for this class, every rider must have N/A; otherwise, replace N/A with the result.",
    "blank-mixed": (issue) => "blank " + rows(issue.rows) + " while other riders have a result. Once the event is raced, every rider must have a value: the points, DNF, DNS or X (absent). Leave the whole column blank only if the event hasn't been held yet.",
    "event-partial": (issue) => "results entered for " + list(issue.entered) + ", not yet for " + list(issue.pending) + ". Write their results (or N/A for every rider of a class that didn't race it): until then, those classes are shown without this race, and it doesn't count as run.",
    "rider-twice": (issue) => quote(issue.found) + " is already in this class at row " + String(issue.firstRow) + " (same name, ignoring accents and capitals). Merge or delete one of the rows.",
    "cancelled-race-value": (issue) => "several riders have " + String(issue.top) + " points, so the class didn't run this race: only " + String(issue.top) + " and X are allowed (found " + quote(issue.found) + ").",
    "top-twice": (issue) => "1st place (" + String(issue.top) + " points) is given to " + String(issue.riders.length) + " riders: " + issue.riders.map((r) => "row " + String(r.row) + " (" + r.name + ")").join(", ") + ". Only one rider can finish 1st (art. 5.67). If the class didn't run this race, give " + String(issue.top) + " to every rider present and X to the others.",
    "points-off-scale": (issue) => quote(issue.found) + " isn't on the points scale (" + list(issue.scale) + " or 0).",
    "points-twice": (issue) => "the same points were given to several riders: " + list(issue.points) + ".",
    "points-missing": (issue) => "no rider has " + list(issue.points) + " points while lower places were given (a missing rider or a typo?).",
    "zero-while-last-free": (issue) => "a rider has 0 points while the last paid place (" + String(issue.last) + " pt) is free.",
    "joker-class-missing": (issue) => "Joker: the class " + quote(issue.found) + " of the configuration isn't in the sheet.",
    "joker-event-missing": (issue) => "Joker: the event " + quote(issue.found) + " of the configuration isn't in the sheet.",
    "number-format": (issue) => "the number " + quote(issue.found) + " has the format " + quote(issue.format) + ", which the site can't show exactly as the sheet does. Until the cell's format is Automatic (or Date, yyyy-mm-dd, for a date), what it touches isn't shown (the class, the championship or the race).",
    "tab-missing": (issue) => "the tab " + quote(issue.found) + " isn't in the workbook. Without it, nothing can be shown: create it (exact name, in capitals).",
    "header-missing": (issue) => "the header " + quote(issue.expected) + " is missing or misspelled (exact name, in capitals, no extra space). Nothing can be shown while it is missing.",
    "cell-empty": () => "the cell is empty.",
    "season-invalid": (issue) => quote(issue.found) + " isn't a season: write the year, for example 2026.",
    "order-invalid": (issue) => quote(issue.found) + " isn't an order: write a whole number (1, 2, 3…).",
    "order-twice": (issue) => "the order " + quote(issue.found) + " is already given at row " + String(issue.firstRow) + " for the same season. Each championship must have its own order.",
    "championship-twice": (issue) => "the championship " + quote(issue.found) + " is already at row " + String(issue.firstRow) + " for the same season. This row is ignored.",
    "tab-not-found": (issue) => "the tab " + quote(issue.found) + " isn't in the workbook (the name must be exactly the tab's).",
    "rules-missing": (issue) => "the " + issue.rulebook + " rules of " + issue.found + " aren't programmed yet. The championship isn't shown: ask for them to be added to the site.",
    "url-invalid": (issue) => quote(issue.found) + " isn't a web address (it must start with https://).",
    "color-invalid": (issue) => quote(issue.found) + " isn't a colour: write it as #RRGGBB, for example #D9302A.",
    "date-invalid": (issue) => quote(issue.found) + " isn't a date: write it yyyy-mm-dd, for example 2026-05-15.",
    "dates-reversed": (issue) => "the end (" + issue.end + ") is before the start (" + issue.start + "). The race isn't shown.",
    "championship-unknown": (issue) => quote(issue.found) + " isn't a championship of the " + issue.season + " season in the CONFIG tab (codes separated by commas, for example “CSM, SMQ”).",
    "event-overlap": (issue) => "the race " + quote(issue.found) + " is already at row " + String(issue.firstRow) + " for " + list(issue.codes) + ". A race can be only once per championship. This row is ignored.",
    "race-days-empty": () => "the race days aren't given (column RACE DAYS: the day or days of the finals, for example “2026-09-19, 2026-09-20”). The site shows the weekend" + RACE_DAYS_FALLBACK,
    "race-day-invalid": (issue) => "race days: " + quote(issue.found) + " isn't a date (yyyy-mm-dd, separated by commas, for example “2026-09-19, 2026-09-20”). The site shows the weekend" + RACE_DAYS_FALLBACK,
    "race-day-outside": (issue) => "race days: " + issue.found + " isn't between the start (" + issue.start + ") and the end (" + issue.end + "). The site shows the weekend" + RACE_DAYS_FALLBACK,
    "race-days-order": (issue) => "race days: " + quote(issue.found) + " must give each day once, from the first to the last. The site shows the weekend" + RACE_DAYS_FALLBACK,
    "language-unknown": (issue) => "the site has no language " + quote(issue.found) + ": nothing reads this column. Its languages: " + list(issue.languages.map((l) => l.toUpperCase())) + ".",
    "classes-missing": (issue) => CLASSES_PLACE(issue.configRow) + "the tab's class " + quote(issue.found) + " isn't in the list: it is shown after the others. Add it in its place (exact name).",
    "classes-twice": (issue) => CLASSES_PLACE(issue.configRow) + quote(issue.found) + " is in the list more than once: only its first place counts.",
    "season-twice": (issue) => "the season " + quote(issue.found) + " is already at row " + String(issue.firstRow) + ". This row is ignored.",
    "rulebook-unknown": (issue) => "the site doesn't know the rulebook " + quote(issue.found) + " (its rules aren't programmed): the season's championships aren't shown.",
    "season-unknown": (issue) => "the season " + quote(issue.found) + " isn't in the SEASONS tab (the list of seasons): this row is ignored.",
    "venues-missing": () => "the tab “VENUES” isn't in the workbook: the races show without their place (exact name, in capitals).",
    "venues-header": (issue) => "the header " + quote(issue.expected) + " is missing or misspelled (exact name, in capitals, no extra space). While it is missing, the races show without their place.",
    "venue-twice": (issue) => "the code " + quote(issue.found) + " is already at row " + String(issue.firstRow) + ". This row is ignored.",
    "coordinates-invalid": (issue) => quote(issue.found) + " isn't a point on a map: copy it from Google Maps, two numbers separated by a comma, for example “46.37123, -72.58034”. The place shows without its map link.",
    "venue-unknown": (issue) => quote(issue.found) + " isn't a CODE of the VENUES tab: the race shows without its place.",
    "gala-header": (issue) => "the header " + quote(issue.expected) + " is missing or misspelled (SEASON, CODE, PUBLIC: exact names, in capitals). While it is missing, no season has Awards.",
    "gala-season-unknown": (issue) => quote(issue.found) + " isn't a season of the SEASONS tab: this row is ignored.",
    "gala-season-twice": (issue) => "the season " + quote(issue.found) + " is already at row " + String(issue.firstRow) + ": only the first row counts. Delete this one.",
    "gala-public-invalid": (issue) => (issue.found === "" ? "the publication date is empty" : quote(issue.found) + " isn't a date") + ": write yyyy-mm-dd (public from midnight) or yyyy-mm-dd hh:mm, for example 2026-11-14 23:00. Until then, the Awards never become public by themselves; the code still opens them.",
    "gala-code-empty": () => "the code is empty: no one can open the Awards before the publication date.",
    "highlights-missing": (issue) => "there is no HIGHLIGHTS tab, while the GALA tab has " + (issue.seasons.length === 1 ? "the season " : "the seasons ") + issue.seasons.join(", ") + ". Create it: one row per highlight and per season, the 9 computed highlights included.",
    "highlights-header": (issue) => "the header " + quote(issue.expected) + " is missing or misspelled (SEASON, CODE, TYPE, SHOW, VALUE, NAMES, then TITLE, UNIT, DETAIL and EXPLANATION in each language of the site: TITLE EN, TITLE FR…; exact names, in capitals).",
    "highlights-season-unknown": (issue) => quote(issue.found) + " isn't a season of the SEASONS tab: there is no telling which season this row belongs to.",
    "highlights-none": (issue) => "the season " + issue.season + " is in the GALA tab but has no row in HIGHLIGHTS. Each season of the gala lists its highlights, the 9 computed ones included: " + HIGHLIGHT_CODES.join(", ") + ".",
    "highlight-twice": (issue) => quote(issue.found) + " is already at row " + String(issue.firstRow) + " for this season: a highlight appears once per season.",
    "show-invalid": (issue) => (issue.found === "" ? "SHOW is empty" : quote(issue.found) + " is neither YES nor NO") + ": write YES to show this highlight, NO to hide it.",
    "trophy-invalid": (issue) => quote(issue.found) + " is neither YES nor NO: write YES if a trophy goes to this highlight, else NO or nothing.",
    "type-invalid": (issue) => (issue.found === "" ? "TYPE is empty" : quote(issue.found) + " is neither COMPUTED nor CUSTOM") + ": write COMPUTED for a highlight the site computes (" + HIGHLIGHT_CODES.join(", ") + "), CUSTOM for one of the organizers'.",
    "highlight-unknown": (issue) => issue.found + " isn't a highlight the site computes: with TYPE COMPUTED, the CODE must be one of the 9 (" + HIGHLIGHT_CODES.join(", ") + ").",
    "highlight-reserved": (issue) => issue.found + " is a highlight the site computes: with TYPE CUSTOM, it needs another CODE (" + HIGHLIGHT_CODES.join(", ") + " are the site's).",
    "highlight-missing": (issue) => "the season " + issue.season + " has no row for " + issue.found + ". Each season of the gala lists the 9 computed highlights: " + HIGHLIGHT_CODES.join(", ") + ".",
    "highlight-computed-content": (issue) => issue.found + " is a computed highlight (TYPE COMPUTED): the site writes its figure, its names and its texts itself. This cell must stay empty.",
    "highlight-value-invalid": (issue) => "VALUE is " + quote(issue.found) + ": the big figure must be a whole number, without spaces or a unit (the unit goes in UNIT).",
    "unit-without-value": (issue) => "the unit is " + quote(issue.found) + " but VALUE is empty: a highlight without a figure has no unit, one with a figure has both.",
    "highlight-incomplete": (issue) => issue.found + " is one of the organizers' highlights (TYPE CUSTOM): these cells are empty: " + issue.empty.join(", ") + ".",
    "distances-missing": (issue) => "there is no DISTANCES tab, while the show of the " + (issue.seasons.length === 1 ? "season " : "seasons ") + issue.seasons.join(", ") + " draws the tour on a map: create it, one row per pair of places (FROM, TO, KM: the road km).",
    "distances-header": (issue) => "the header " + quote(issue.expected) + " is missing or misspelled (FROM, TO and KM; exact names, in capitals).",
    "distance-venue-unknown": (issue) => quote(issue.found) + " isn't a CODE of the VENUES tab: this row is ignored.",
    "distance-same": (issue) => "FROM and TO are both " + quote(issue.found) + ": a row joins two different places.",
    "distance-invalid": (issue) => "KM is " + quote(issue.found) + ": write the road km as a whole number, digits only (35, not “35 km”).",
    "distance-twice": (issue) => issue.found + " is already at row " + String(issue.firstRow) + " (one row serves both ways): this row is ignored.",
    "distance-none": (issue) => "the " + issue.season + " show's map goes from " + issue.from + " to " + issue.to + " (two races in a row), but DISTANCES has no row for these two places.",
    "promotions-header": (issue) => "the header " + quote(issue.expected) + " is missing or misspelled (SEASON, NAME, DISCIPLINE, FROM and TO; exact names, in capitals).",
    "promotions-season-unknown": (issue) => quote(issue.found) + " isn't a season of the SEASONS tab: there is no telling which season this promotion belongs to.",
    "discipline-unknown": (issue) => quote(issue.found) + " isn't a discipline of this season's rulebook" + (issue.disciplines.length ? " (" + issue.disciplines.join(", ") + ", exactly)." : "."),
    "level-invalid": (issue) => quote(issue.found) + " isn't a level of " + issue.discipline + " (" + issue.levels.join(", ") + ", exactly).",
    "promotion-not-up": (issue) => "FROM is " + issue.from + " and TO is " + issue.to + ": a promotion goes up a level, never down nor to the same one (art. 1.7).",
    "promotion-twice": (issue) => quote(issue.found) + " is already promoted in " + issue.discipline + " at row " + String(issue.firstRow) + " for this season.",
    "promotion-name-unknown": (issue) => quote(issue.found) + " isn't the NAME of any rider in the " + issue.season + " championship tabs, written exactly the same way (capitals and accents included).",
    "promotion-no-discipline": (issue) => issue.found + " has no row in a class of " + issue.discipline + " this season: the licence shows the plates of all their rows.",
    "media-kit-header": (issue) => "the header " + quote(issue.expected) + " is missing or misspelled (SEASON, NAME and CODE; exact names, in capitals).",
    "media-kit-season-unknown": (issue) => quote(issue.found) + " isn't a season of the SEASONS tab: there is no telling which season this row belongs to.",
    "media-kit-twice": (issue) => quote(issue.found) + " is already listed at row " + String(issue.firstRow) + " for this season.",
    "media-kit-name-unknown": (issue) => quote(issue.found) + " isn't the NAME of any rider in the " + issue.season + " championship tabs, written exactly the same way (capitals and accents included): no media kit button.",
    "media-kit-code-empty": (issue) => issue.found + " has no CODE: their page offers no media kit (the form asks for this code before it opens).",
    "media-kit-code-twice": (issue) => issue.found + " has the same CODE as " + issue.other + " (row " + String(issue.firstRow) + "): either one could open the other's media kit form.",
    "sponsors-missing": (issue) => "there is no SPONSORS tab, while the GALA tab has " + (issue.seasons.length === 1 ? "the season " : "the seasons ") + issue.seasons.join(", ") + ": the show thanks no partner. Create it: one row per partner and per season.",
    "sponsors-header": (issue) => "the header " + quote(issue.expected) + " is missing or misspelled (SEASON, NAME and LOGO, then TIER and SINCE if you use them; exact names, in capitals).",
    "sponsors-season-unknown": (issue) => quote(issue.found) + " isn't a season of the SEASONS tab: there is no telling which season this partner belongs to.",
    "sponsors-none": (issue) => "the season " + issue.season + " is in the GALA tab but has no partner in SPONSORS: its show thanks no partner.",
    "sponsor-twice": (issue) => quote(issue.found) + " is already at row " + String(issue.firstRow) + " for this season: a partner appears once per season, so this row is ignored.",
    "logo-unknown": (issue) => (issue.found === "" ? "LOGO is empty" : quote(issue.found) + " isn't one of the site's logo files") + ": the show writes the partner's name instead of its logo.",
    "tier-invalid": (issue) => quote(issue.found) + " isn't a tier: write PLATINUM, GOLD, SILVER, BRONZE or A LA CARTE. Until then, the show puts the partner on the wall, without a tier.",
    "since-invalid": (issue) => "SINCE is " + quote(issue.found) + ": write a year (2019) or leave it empty. Until then, the show says nothing of since when.",
    "since-after": (issue) => "SINCE is " + issue.found + ", after the season " + issue.season + ": a partner of this season started that year or before. Until then, the show says nothing of since when.",
    "gala-order-header": (issue) => "the header " + quote(issue.expected) + " is missing or misspelled (exact name, in capitals, no extra space). While it is missing, Champions' Night shows the championships in SITE ORDER.",
    "gala-classes-header": (issue) => "the header " + quote(issue.expected) + " is missing or misspelled (exact name, in capitals, no extra space). While it is missing, Champions' Night shows each championship's classes in SITE CLASS ORDER.",
    "gala-classes-missing": (issue) => "the class " + quote(issue.found) + " of the season's tabs isn't in the list: Champions' Night shows it after the others. Add it in its place (exact name).",
    "gala-classes-twice": (issue) => quote(issue.found) + " is in the list more than once: only its first place counts.",
    "column-no-calendar": (issue) => "the column " + quote(issue.found) + " of the tab " + quote(issue.tab) + " has no row in CALENDAR for this championship (the same race name, exactly).",
    "calendar-no-column": (issue) => "the CALENDAR race " + quote(issue.found) + " has no column in the tab " + quote(issue.tab) + " (the same name, exactly).",
    "column-order": (issue) => "the columns of the tab " + quote(issue.tab) + " aren't in the order of the CALENDAR dates (" + list(issue.events) + " instead of " + list(issue.expected) + "). The tiebreak reads the last race (art. 19.4): the championship isn't shown until the order is fixed.",
    "name-spelling": (issue) => "the same rider is written in several ways: " + variants(issue.variants) + ". Write it the same everywhere.",
    "name-similar": (issue) => "probably the same rider: " + variants(issue.variants) + ". If so, write the same name everywhere; otherwise, ignore this warning.",
    "class-spelling": (issue) => "the same class is written in several ways: " + variants(issue.variants) + ".",
    "event-spelling": (issue) => "the same race is written in several ways: " + variants(issue.variants) + ".",
    "shared-outcome": (issue) => issue.event + " is a shared round (one final, art. 13.2 IMPORTANT), but the rider's result differs: " + issue.values.map((v) => v.code + " " + quote(v.found)).join(", ") + ". Put the same value in every championship of the round. The class isn't shown in any of them until this is fixed.",
    "shared-missing": (issue) => issue.event + " is a shared round (one final, art. 13.2 IMPORTANT): the rider has " + issue.values.map((v) => v.code + " " + quote(v.found)).join(", ") + " but no row in the " + issue.missing.map((m) => m.code).join(", ") + " tab. Add the row with the same value, or fix the name if it is written differently. The class isn't shown in any of these championships until this is fixed.",
    "shared-pending": (issue) => issue.event + " is a shared round (one final): the result is entered in " + issue.values.filter((v) => v.found !== "").map((v) => v.code + " " + quote(v.found)).join(", ") + " but not yet in " + issue.values.filter((v) => v.found === "").map((v) => v.code).join(", ") + ". Enter the same value there."
  };
  var and = (items) => items.length < 2 ? items.join("") : items.slice(0, -1).join(", ") + " and " + items[items.length - 1];
  function happened(rider, value) {
    if (value === "0") return rider + " finished outside the points (0 pt)";
    if (/^\d+(\.\d+)?$/.test(value)) return rider + " finished the final (" + value + " pts)";
    if (value === "DNF") return rider + " started but didn't finish (DNF)";
    if (value === "DNS") return rider + " didn't start (DNS)";
    if (value === "X") return rider + " wasn't at this round (X)";
    if (value === "N/A") return "The final wasn't held for this class (N/A)";
    return quote(value) + " is the right value";
  }
  var cards = {
    intro: "Each card is a problem to fix in the Google workbook, with the exact cells and the ways to fix it. Tick “Fixed” as you go: it only helps you keep track, nothing is saved. Reload the page to check the workbook again.",
    doneCount: (done, total) => String(done) + " fixed of " + String(total),
    goTo: "Go to",
    done: "Fixed",
    unfold: "Show",
    fold: "Hide",
    error: "Error",
    warning: "Warning",
    errorsTitle: "To fix: until then, what they touch isn't shown on the site",
    warningsTitle: "To check: the standings are shown",
    workbookPart: "The workbook",
    workbookNav: "Workbook",
    seasonsTab: "Tab “SEASONS”",
    venuesTab: "Tab “VENUES”",
    configTab: "Tab “CONFIG”",
    calendarTab: "Tab “CALENDAR”",
    clean: "No problem:",
    allClean: "No problem:",
    betweenHelp: "The shared rounds, names written two ways, and the columns compared with the calendar.",
    namesAndColumns: "Names and columns",
    ref: (tab, col, row2) => tab + (col !== void 0 && col >= 0 ? ", " + colLetter(col) + (row2 !== void 0 ? String(row2) : "") : row2 !== void 0 ? ", row " + String(row2) : ""),
    head: { rider: "Rider", cell: "Cell", value: "Value", name: "Name as written", where: "Where", category: "Class" },
    entered: "entered",
    plate: (plate3) => "No. " + plate3,
    noRow: "no row",
    blank: "empty",
    more: (n) => n === 1 ? "Show the other rider" : "Show the " + String(n) + " other riders",
    changes: (n) => n === 1 ? "Show the cell to change" : "Show the " + String(n) + " cells to change",
    change: (ref2, from, to) => ref2 + ": " + (from === "" ? "empty" : quote(from)) + " → " + quote(to),
    addRowAt: (tab, rider, value) => tab + ": add " + rider + "'s row, with " + quote(value),
    deleteRowAt: (ref2, rider) => ref2 + ": delete " + rider + "'s row",
    blocksTitle: "What it blocks",
    fixTitle: "What to do?",
    galaSeason: (season) => "Season " + season,
    places: {
      title: "The places",
      aside: (n) => plural(n, "place", "places"),
      help: "Open each point once on the map: the site can't know a wrong digit (it would be another place).",
      head: { code: "Code", name: "Name", address: "Address", map: "Map" },
      see: "See on the map ↗",
      noPoint: "No point"
    },
    happened,
    titles: {
      "empty-sheet": "Empty tab",
      "header-label": "Header written wrong",
      "no-events": "No race in the header",
      "event-empty": "Race without a name",
      "event-reserved": "Race name not allowed",
      "event-twice": "Race twice",
      spaces: "Extra spaces",
      "outside-table": "Something right of the table",
      "category-empty": "Empty class",
      "name-empty": "Empty rider name",
      "name-case": "Name not in capitals",
      "value-invalid": "Value not allowed",
      "na-mixed": "N/A for some riders only",
      "blank-mixed": "Result missing",
      "event-partial": "Race not entered for every class yet",
      "rider-twice": "Rider twice",
      "cancelled-race-value": "Race not run: value not allowed",
      "top-twice": "Several winners",
      "points-off-scale": "Points off the scale",
      "points-twice": "Same points for several riders",
      "points-missing": "A place without a rider",
      "zero-while-last-free": "0 points while a paid place is free",
      "joker-class-missing": "Joker: class not found",
      "joker-event-missing": "Joker: race not found",
      "number-format": "Number format",
      "tab-missing": "Tab missing",
      "header-missing": "Header missing",
      "cell-empty": "Empty cell",
      "season-invalid": "Season can't be read",
      "order-invalid": "Order can't be read",
      "order-twice": "Same order for two championships",
      "championship-twice": "Championship twice",
      "tab-not-found": "Tab not found",
      "rules-missing": "Rules not programmed yet",
      "url-invalid": "Invalid web address",
      "color-invalid": "Invalid colour",
      "date-invalid": "Date can't be read",
      "dates-reversed": "End before start",
      "championship-unknown": "Unknown championship",
      "event-overlap": "Race twice",
      "race-days-empty": "Race days missing",
      "race-day-invalid": "Race day can't be read",
      "race-day-outside": "Race day outside the weekend",
      "race-days-order": "Race days out of order",
      "language-unknown": "Column of an unknown language",
      "classes-missing": "Class missing from SITE CLASS ORDER",
      "classes-twice": "Class twice in SITE CLASS ORDER",
      "season-twice": "Season twice",
      "rulebook-unknown": "Rulebook not programmed yet",
      "season-unknown": "Season not listed",
      "venues-missing": "Tab missing",
      "venues-header": "Header missing",
      "venue-twice": "Place twice",
      "coordinates-invalid": "Point can't be read",
      "venue-unknown": "Unknown place",
      "gala-header": "Header missing",
      "gala-season-unknown": "Unknown season",
      "gala-season-twice": "Season twice",
      "gala-public-invalid": "Publication date can't be read",
      "gala-code-empty": "Empty code",
      "gala-order-header": "Header missing",
      "gala-classes-header": "Header missing",
      "gala-classes-missing": "Class missing from GALA CLASS ORDER",
      "gala-classes-twice": "Class twice in GALA CLASS ORDER",
      "highlights-missing": "No HIGHLIGHTS tab",
      "highlights-header": "Header missing",
      "highlights-season-unknown": "Unknown season",
      "highlights-none": "Season without highlights",
      "highlight-twice": "Highlight twice",
      "show-invalid": "SHOW is neither YES nor NO",
      "trophy-invalid": "TROPHY is neither YES nor NO",
      "type-invalid": "TYPE is neither COMPUTED nor CUSTOM",
      "highlight-unknown": "Unknown computed highlight",
      "highlight-reserved": "Code reserved for the site",
      "highlight-missing": "Computed highlight missing from the season",
      "highlight-computed-content": "Cell to leave empty",
      "highlight-value-invalid": "VALUE isn't a whole number",
      "highlight-incomplete": "Organizers' highlight incomplete",
      "distances-missing": "No DISTANCES tab",
      "distances-header": "Header missing",
      "distance-venue-unknown": "Unknown place",
      "distance-same": "The same place twice",
      "distance-invalid": "KM isn't a whole number",
      "distance-twice": "Distance twice",
      "distance-none": "Distance missing",
      "sponsors-missing": "No SPONSORS tab",
      "promotions-header": "Header missing",
      "promotions-season-unknown": "Unknown season",
      "discipline-unknown": "Unknown discipline",
      "level-invalid": "Unknown level",
      "promotion-not-up": "Not a promotion",
      "promotion-twice": "Promotion twice",
      "promotion-name-unknown": "Rider not found",
      "promotion-no-discipline": "Rider outside the discipline",
      "media-kit-header": "Header missing",
      "media-kit-season-unknown": "Unknown season",
      "media-kit-twice": "Rider listed twice",
      "media-kit-name-unknown": "Rider not found",
      "media-kit-code-empty": "Code missing",
      "media-kit-code-twice": "Same code twice",
      "sponsors-header": "Header missing",
      "sponsors-season-unknown": "Unknown season",
      "sponsors-none": "Season without partners",
      "sponsor-twice": "Partner twice",
      "logo-unknown": "Unknown logo",
      "tier-invalid": "Unknown tier",
      "since-invalid": "SINCE isn't a year",
      "since-after": "SINCE after the season",
      "unit-without-value": "Unit without a figure",
      "column-no-calendar": "Column not in the calendar",
      "calendar-no-column": "Calendar race without a column",
      "column-order": "Columns not in date order",
      "name-spelling": "Same rider written several ways",
      "name-similar": "Probably the same rider",
      "class-spelling": "Same class written several ways",
      "event-spelling": "Same race written several ways",
      "shared-outcome": "Result differs between championships",
      "shared-missing": "Rider missing from a championship of the round",
      "shared-pending": "Result not entered everywhere yet"
    },
    shared: {
      outcome: (event) => event + " is a shared round: one final, so the same result in each championship (art. 13.2 IMPORTANT). Here the cells differ.",
      missing: (event, codes) => event + " is a shared round (art. 13.2 IMPORTANT): the rider has a result, but no row in their class in " + and(codes) + ".",
      pending: (event) => event + " is a shared round: the result is entered in one championship, not yet in the other.",
      columnTitle: (event, category, codes, riders) => "The " + event + " column of " + category + " differs between " + and(codes) + " for " + plural(riders, "rider", "riders"),
      columnCause: (rider, sides) => {
        const results2 = sides.filter((s) => s.value !== null && s.value !== "X");
        const none = sides.filter((s) => s.value === null || s.value === "X");
        const said = (s) => (s.value === null ? "no row" : quote(s.value)) + " in " + and(s.codes);
        return "Probable cause: " + rider + " has " + and(results2.map(said)) + ", but " + and(none.map(said)) + ". In " + and(none.flatMap((s) => s.codes)) + ", the riders behind were probably counted again without this rider: they have one place better there. One final (art. 13.2 IMPORTANT): the cells must be the same everywhere.";
      },
      columnNoCause: (event) => event + " is a shared round: one final, so the same cells in each championship (art. 13.2 IMPORTANT). Several riders have different cells.",
      pendingTitle: (event, category, empty) => "The " + event + " column of " + category + " isn't entered yet in " + and(empty),
      pendingColumn: (event, full, empty, riders) => event + " is a shared round: " + plural(riders, "result is", "results are") + " entered in " + and(full) + ", but the column is still empty in " + and(empty) + ".",
      namesTitle: (a, b) => "The same rider written " + quote(a) + " and " + quote(b) + "?",
      names: (a, b, events) => quote(a) + " and " + quote(b) + " have the same number (or the same result) at " + and(events) + ", each in only one championship of the round: probably the same rider, written two ways. While the names differ, the site counts two different riders."
    },
    blocks: {
      nothing: "Nothing: the standings are shown.",
      partial: (event, pending) => "Nothing: " + and(pending) + " " + (pending.length > 1 ? "are" : "is") + " shown without " + event + " until entered; the race doesn't count as run yet.",
      raceDays: "Nothing: the site shows the weekend (START–END) instead of the race days.",
      everything: "The whole site: nothing is shown until it is fixed.",
      row: "This row is ignored.",
      championship: (code, season) => "The " + code + " " + season + " championship isn't shown.",
      race: (event, season) => "The race " + event + " " + season + " isn't shown (neither in the calendar nor in the standings).",
      galaPublic: (season) => "The " + season + " Awards never become public by themselves (the code still opens them).",
      galaNone: "No season has Awards.",
      highlights: (seasons) => (seasons.length === 0 ? "The highlights of every season" : "The highlights of the " + (seasons.length === 1 ? "season " : "seasons ") + seasons.join(", ")) + ": none is shown, neither on the Awards page nor in the show, until it is fixed. Nothing of the standings.",
      sponsorRow: (name2, season) => name2 + " isn't thanked in the " + season + " show until it is fixed. Nothing of the standings.",
      sponsorsAll: "No partner is thanked in any show until it is fixed. Nothing of the standings.",
      distance: (from, to) => "Nothing of the standings: the show's map draws " + from + " – " + to + " without its km, and the tour without its total.",
      distancesAll: "Nothing of the standings: the show's map shows no km until it is fixed.",
      promotions: (seasons) => (seasons.length === 0 ? "The promotions of every season" : "The promotions of the " + (seasons.length === 1 ? "season " : "seasons ") + seasons.join(", ")) + ": none is announced in the show until it is fixed. Nothing of the standings.",
      promotionPlates: (name2) => "Nothing of the standings: " + name2 + "'s licence in the show has the plates of all their rows.",
      mediaKit: (name2) => "Nothing of the standings: " + (name2 === "" ? "this row" : name2 + "'s page") + " offers no media kit until it is fixed.",
      mediaKitAll: "Nothing of the standings: no rider's page offers the media kit until it is fixed.",
      noSponsors: (seasons) => "Nothing of the standings: the show of the " + (seasons.length === 1 ? "season " : "seasons ") + seasons.join(", ") + " thanks no partner.",
      logoName: (name2) => "Nothing of the standings: the show writes " + name2 + " in big letters instead of its logo.",
      tierFallback: (name2) => "Nothing of the standings: the show puts " + name2 + " on the wall, without a tier.",
      sinceNone: (name2) => "Nothing of the standings: the show says nothing of since when " + name2 + " is a partner.",
      showChamps: "Nothing of the standings: Champions' Night shows the championships in SITE ORDER.",
      showClasses: "Nothing of the standings: Champions' Night shows each championship's classes in SITE CLASS ORDER.",
      seasonRules: (season) => "No championship of the " + season + " season is shown.",
      places: "Nothing of the standings: the races show without their place.",
      place: (event, season) => "Nothing of the standings: the race " + event + " " + season + " shows without its place.",
      mapLink: (code) => "Nothing of the standings: the place " + code + " shows without its map link.",
      classIn: (category, codes) => category + " isn't shown in " + (codes.length > 1 ? and(codes) : codes.join("")) + "."
    },
    fix: {
      always: "In any case",
      ignore: "nothing to change: this warning blocks nothing",
      wanted: "It's intended (a decision of Race Direction)",
      typo: "A typing mistake",
      put: (value, refs) => "put " + quote(value) + " in " + and(refs),
      clear: (at2) => "empty " + at2,
      fill: (at2, header2) => "write the " + header2 + " in " + at2,
      raced: (rider, codes) => rider + " raced the final and is entered in " + and(codes),
      addRow: (tabs2, category, plate3, rider, event, col, value) => "add their row in " + and(tabs2) + ": CATEGORY " + quote(category) + ", # " + quote(plate3) + ", NAME " + quote(rider) + ", and " + quote(value) + " in column " + col + " (" + event + ")",
      otherName: (rider, codes) => rider + " is already in " + and(codes) + ", but the name is written another way there",
      sameName: (rider) => "write the name exactly " + quote(rider) + " there",
      didNotRace: (rider) => rider + " didn't race this final",
      enterNow: "The result is known",
      sideRight: (codes) => "The cells of " + and(codes) + " are the right ones",
      copyColumn: (event, category, from, to, cells) => "copy the " + event + " column of " + category + " from " + and(from) + " to " + and(to) + " (" + plural(cells, "cell", "cells") + ")",
      sameRider: (name2) => "It's the same rider, and the name is " + quote(name2),
      rename: (name2, refs) => "write " + quote(name2) + " in " + and(refs),
      twoRiders: "They are two different riders",
      addBoth: (names, events) => "give each one a row in the other championship, with the same result at " + and(events) + " (" + and(names) + ")",
      twoRidersSimilar: (names) => and(names.map(quote)) + " are two different riders",
      writeHeader: (header2, tab) => "write " + quote(header2) + " in row 1 of the tab " + quote(tab) + " (exact names, in capitals)",
      addColumns: (tab) => "add the races after NAME in row 1 of the tab " + quote(tab) + ", one column each, in date order",
      nameEvent: (at2) => "write in " + at2 + " the race's name, exactly as its EVENT in CALENDAR",
      sameEvent: "It's the same race twice",
      otherEvent: "They are two different races",
      mergeColumns: (first, at2) => "keep the results in " + first + " and delete the column of " + at2,
      meantPoints: (points2) => "You meant " + points2 + " (digits, not letters)",
      meantSpecial: (value) => "You meant " + value + " (in capitals)",
      otherValue: "It's something else",
      allowed: (at2) => "write in " + at2 + " the points (in digits), DNF, DNS, X, N/A, or leave it empty",
      notHeld: "The race wasn't held for this class",
      held: "The race was held for this class",
      replaceNa: (column) => "replace each N/A of the column " + column + " with the rider's result",
      enterResults: (refs) => "write the result (points, DNF, DNS or X) in " + and(refs),
      enterClasses: (classes, column) => "write the results of " + and(classes) + " in the column " + column,
      naForClass: (column) => "put N/A for each of its riders in the column " + column,
      sameRiderTwice: "It's the same rider",
      mergeRows: (first, at2) => "keep one row: put the results in " + first + ", then delete " + at2,
      twoNamesakes: "They are two riders with the same name",
      tellApart: (at2) => "write the name differently in " + at2 + " (a second first name, an initial…)",
      notRun: "The class didn't run this race",
      run: "The class ran this race",
      cancelled: (top2, refs) => "put " + String(top2) + " for each rider present and X for the others in " + and(refs),
      oneWinner: (top2, refs) => "keep " + String(top2) + " for the winner only and give the others their place's points: " + and(refs),
      scale: (at2, scale) => "write in " + at2 + " points of the scale (" + scale.join(", ") + ") or 0",
      samePoints: (points2, column) => "two riders can't finish in the same place: correct one of those with " + points2.join(", ") + " in the column " + column,
      placeMissing: (points2) => "A rider scored " + String(points2) + " pts, but they aren't entered",
      givePoints: (points2, column) => "put " + String(points2) + " in that rider's cell of the column " + column,
      inThePoints: (last) => "The rider finished in the last paid place (" + String(last) + " pt)",
      renamed: "The name changed in the tab",
      writeExactly: (name2, tab) => "write " + quote(name2) + " exactly in the tab " + quote(tab),
      jokerChange: "The Joker's settings must change",
      askSite: "ask for the site's Joker settings to be changed (they are in the site, not in the workbook)",
      formatAuto: (at2) => "select " + at2 + ", then Format › Number › Automatic (or Date, for a date)",
      createTab: (name2) => "create a tab named exactly " + quote(name2) + " (header CATEGORY, #, NAME, then the races)",
      tabTypo: (name2) => "The tab exists as " + quote(name2),
      tabToCreate: "The tab doesn't exist yet",
      writeSeason: (at2) => "write the year in " + at2 + ", e.g. 2026",
      writeOrder: (at2) => "write in " + at2 + " a whole number that no other championship of the season has (1, 2, 3…)",
      deleteRow: (row2, first) => "delete " + row2 + (first ? " (" + first + " counts)" : ""),
      editionTypo: "The edition is written wrong",
      writeEdition: (at2) => "write in " + at2 + " the edition as the site knows it, e.g. 2026",
      newEdition: "It's a new edition of the rulebook",
      askEdition: (rulebook) => "ask for the rulebook " + rulebook + " to be added to the site (add its PDF to the repository)",
      seasonNew: "The season isn't in SEASONS yet",
      addSeasonRow: (season) => "add its row in SEASONS: SEASON " + season + ", its RULEBOOK and RULEBOOK URL",
      createSeasons: "create a tab named exactly “SEASONS” with the headers SEASON, RULEBOOK, RULEBOOK URL",
      createVenues: "create a tab named exactly “VENUES” with the headers CODE, NAME, ADDRESS, COORDINATES",
      venueTypo: "The code is written wrong",
      writeVenueCode: (at2) => "write in " + at2 + " the exact CODE of a row of VENUES",
      venueNew: "The place isn't in VENUES yet",
      addVenueRow: (code) => "add its row in VENUES: CODE " + quote(code) + ", NAME, ADDRESS, COORDINATES",
      copyPoint: (at2) => "in Google Maps, right-click the exact spot (a long press on a phone), click the numbers to copy them, then paste them in " + at2,
      askRules: (rulebook, code) => "ask for the rules " + rulebook + " of the " + code + " to be added to the site (add the rulebook's PDF to the repository)",
      writeUrl: (at2) => "write in " + at2 + " a full web address, starting with https://",
      writeColor: (at2) => "write in " + at2 + " a colour as #RRGGBB, e.g. #D9302A",
      dateGuess: (date) => "The date is " + date,
      writeDate: (at2) => "write the date in " + at2 + " as yyyy-mm-dd, e.g. 2026-05-15",
      swapDates: (start, end) => "swap " + start + " and " + end,
      codeTypo: "The code is written wrong",
      writeCode: (at2, season) => "write in " + at2 + " the exact CODE of a " + season + " row of CONFIG",
      codeMissing: "The championship isn't in CONFIG yet",
      addConfigRow: (code, season) => "add its row in CONFIG: SEASON " + season + ", CODE " + quote(code) + ", and the rest",
      raceDaysKnown: "You know the day or days of the finals",
      raceDays: (at2, start, end) => "write them in " + at2 + ", between " + start + " and " + end + " (yyyy-mm-dd, separated by commas: " + quote(end) + ")",
      raceDaysUnknown: "You don't know them yet",
      languageTypo: "The language's code is written wrong",
      writeLanguage: (at2, languages) => "write in " + at2 + " the header with one of the site's languages (" + languages.map((l) => l.toUpperCase()).join(", ") + ")",
      languageUnneeded: "The column isn't needed",
      deleteColumn: (at2) => "delete the column of " + at2,
      addClass: (name2, at2) => "add " + quote(name2) + " to the list in " + at2 + ", at its place",
      removeClass: (name2, at2) => "keep " + quote(name2) + " only once in the list in " + at2,
      seasonTypo: "The season is written wrong",
      rowUnneeded: "The row isn't needed",
      writePublic: (at2) => "write in " + at2 + " the date and time the Awards become public, e.g. 2026-11-14 23:00",
      writeKitCode: (at2) => "write in " + at2 + " the code you give this rider: they type it to open the form",
      changeKitCode: (at2, other) => "give one of them another code, in " + at2 + " or in " + other,
      writeGalaCode: (at2) => "write a code in " + at2 + ": the organizers type it to see the Awards before then",
      createHighlights: "create the HIGHLIGHTS tab: on row 1, SEASON, CODE, TYPE, SHOW, VALUE, NAMES, then TITLE, UNIT, DETAIL and EXPLANATION of each language (TITLE EN, TITLE FR…); then one row per highlight and per season, in the order they must come",
      addHighlights: (season) => "add the rows of the season " + season + " in HIGHLIGHTS: the 9 computed highlights (" + HIGHLIGHT_CODES.join(", ") + "), each with COMPUTED and YES or NO, and your own (CUSTOM), in the order they must come",
      wantShown: "You want to show it",
      wantTrophy: "A trophy goes to it",
      noTrophy: "No trophy goes to it",
      wantHidden: "You don't want to show it",
      addHighlight: (season, code, show) => "add the row " + season + " · " + code + " · COMPUTED" + (show ? " · YES where it must come in the tab" : " · NO, anywhere in the season"),
      sameHighlight: "It is the same highlight",
      otherHighlight: "They are two different highlights",
      renameCode: (at2) => "give it another CODE in " + at2,
      writeShow: (at2) => "write YES or NO in " + at2,
      valueIs: (value) => "The figure is " + value,
      writeValue: (at2) => "write a whole number in " + at2 + ", without spaces or a unit",
      wasComputed: (code) => "It was the computed highlight " + code,
      ownHighlight: "It is one of yours",
      siteComputes: "The site computes this highlight",
      hasFigure: "The highlight has a figure",
      noFigure: "The highlight has no figure",
      makeCustom: (at2) => "write CUSTOM in " + at2 + ", then fill in NAMES and the texts of each language (VALUE and UNIT if it has a figure)",
      makeComputed: (at2) => "write COMPUTED in " + at2 + " and empty VALUE, NAMES and the texts of the row",
      fillHighlight: (refs) => "fill in " + and(refs) + ": NAMES the names, then the texts of each language (UNIT goes with VALUE)",
      writeOneOf: (at2, values) => "write in " + at2 + " one of " + values.join(", ") + ", exactly",
      levelsSwapped: "FROM and TO are the wrong way round",
      swapLevels: (from, to) => "swap " + from + " and " + to,
      notPromoted: "The rider isn't promoted",
      riderIs: (name2) => "It is " + name2,
      riderNotFound: (season) => "The rider has another name in the " + season + " tabs",
      checkName: (at2) => "write in " + at2 + " the NAME exactly as the championship tab writes it",
      disciplineWrong: "DISCIPLINE is wrong",
      checkDiscipline: (at2) => "correct " + at2,
      createDistances: "create the DISTANCES tab: on row 1, FROM, TO, KM; then one row per pair of places of VENUES (their CODE), with the road km Google Maps gives",
      addDistance: (from, to) => "add a row to DISTANCES: FROM " + from + ", TO " + to + ", KM the road km Google Maps gives between the two places (one row serves both ways)",
      createSponsors: "create the SPONSORS tab: on row 1, SEASON, NAME, LOGO, TIER, SINCE (TIER and SINCE may stay empty); then one row per partner and per season, in the order they must come in each tier",
      addSponsors: (season) => "add the partners of the season " + season + " in SPONSORS, one row each (copy last season's rows and change SEASON)",
      logoIs: (file) => "It is the file " + file,
      logoNew: "It is a new logo",
      askLogo: (name2) => "ask the site's developer to add the logo of " + name2 + " to the site, then write its file's name in LOGO",
      writeTier: (at2) => "write PLATINUM, GOLD, SILVER, BRONZE or A LA CARTE in " + at2,
      sinceKnown: "You know the year it started",
      writeSince: (at2, season) => "write it in " + at2 + " (" + season + " or before)",
      sinceUnknown: "You don't know it",
      calendarRowIgnored: (row2) => "CALENDAR has this race in row " + String(row2) + ", but the row is ignored because of an error",
      seeCalendar: "fix it (see the tab “CALENDAR” above): this warning will go away",
      calendarRowMissing: "The race is missing from CALENDAR",
      addCalendarRow: (season, event, code) => "add a row in CALENDAR: SEASON " + season + ", EVENT " + quote(event) + ", CHAMPIONSHIPS " + quote(code) + ", its dates and venue",
      columnTypo: "The column's name is written wrong",
      renameColumn: (at2) => "write in " + at2 + " the race's name exactly as its EVENT in CALENDAR",
      countsFor: (code) => "The race counts for the " + code,
      addColumn: (event, tab) => "add the column " + quote(event) + " in " + tab + ", at its place in date order",
      notFor: (code) => "The race doesn't count for the " + code,
      removeCode: (code, at2) => "remove " + quote(code) + " from CHAMPIONSHIPS in " + at2,
      reorder: (tab, events) => "put the columns of " + tab + " back in date order: " + events.join(", "),
      rightName: (name2) => "The right name is " + quote(name2),
      writeIn: (name2, places) => "write it " + quote(name2) + " in " + and(places) + " too"
    }
  };
  var jokerHelp = {
    link: "Joker (art. 19.4)",
    article: "art. 19.4",
    dropped: "Joker: dropped from the total",
    riderLine: (event, value, pts2) => "Joker: " + event + " (" + (pts2 > 0 ? String(pts2) + " pts" : value) + ") dropped",
    grossLine: (gross, pts2, net) => "Gross " + gross + (pts2 > 0 ? " − " + String(pts2) : "") + " = " + String(net) + " pts"
  };
  var en = {
    // The page and the logo bar.
    eyebrow: "Canadian Supermoto",
    title: "Standings",
    seasonLabel: "Season",
    seasonPick: (season) => "Season " + season + ", change",
    overviewTile: "OVERVIEW",
    barLabel: "Championships",
    updating: "Standings being updated",
    /** The back-to-top button's name (CSM UI draws it). */
    toTop: "Back to top",
    update: {
      title: "New version of the site",
      left: (time) => "Automatic reload in " + time + ", or at your next click.",
      live: (minutes) => "New version of the site. The page will reload in " + String(minutes) + " minutes, or at your next click.",
      reload: "Reload",
      loading: "Updating the site…",
      loadingSub: "Loading the new version, a few seconds.",
      clock: (minutes, seconds) => String(minutes) + ":" + String(seconds).padStart(2, "0"),
      version: (name2, version) => name2 + " " + version,
      versions: (parts) => parts.join(" · ")
    },
    banner: {
      season: (season) => "Season " + season,
      races: (done, total) => String(done) + " / " + String(total) + " races run"
    },
    favs: {
      add: "Add to favourites",
      added: "In your favourites",
      hint: "Your favourites open with one click on ★ Favourites at the top of every page, on this device.",
      button: "Favourites",
      buttonAria: (n) => "Favourites (" + String(n) + ")",
      title: (n) => "Your favourites" + (n > 1 ? " · " + String(n) : ""),
      empty: (button) => "No favourites yet. On a rider's page, tap " + button + ".",
      device: "On this device only.",
      remove: (name2) => "Remove " + name2 + " from your favourites"
    },
    // The dashboard.
    nextRace: "Next race",
    lastRace: "Last race",
    live: "● Live",
    nextTag: "Next",
    days: "days",
    calendarTitle: (season) => season + " calendar",
    calendarAside: "Swipe to see the whole season",
    findRider: "Find a rider",
    searchPlaceholder: "Find a rider…",
    clearSearch: "Clear the search",
    search: {
      head: "Riders",
      listLabel: "Riders found",
      count: (n) => n === 0 ? "No rider found" : n === 1 ? "1 rider found" : String(n) + " riders found",
      more: (n) => "Narrow the search to see " + (n === 1 ? "the other rider" : "the " + String(n) + " others"),
      hint: "Type part of the rider's name.",
      cancel: "Cancel"
    },
    leaders: "Leaders by class",
    leadersAside: "Tap a class for its standings",
    leadersStakes: (race, open, sure) => "At " + race + ": " + [
      ...open ? [String(open) + (open === 1 ? " title open" : " titles open")] : [],
      ...sure ? [String(sure) + " already clinched"] : []
    ].join(" · "),
    leadersSure: "Title clinched",
    scheduleLabel: (event) => event + " schedule (new tab)",
    // A championship.
    finalStandings: "Final standings",
    /** The status pills (the tiles, the banner, the cards…). */
    phase: { final: "Final", live: "In progress", upcoming: "Upcoming", blocked: "Updating" },
    seasonDone: "Complete",
    /** A tile's pill while races remain: "In progress · 10/11". */
    tileLive: (done, total) => "In progress · " + String(done) + "/" + String(total),
    tileAria: (name2, status) => name2 + " — " + status,
    seasonAria: (season) => "Season " + season,
    seasonStatus: {
      final: "complete",
      live: (done, total) => "in progress, " + String(done) + " of " + String(total) + " races",
      upcoming: "upcoming",
      blocked: "standings being updated"
    },
    inProgress: (done, planned) => "after " + String(done) + " of " + String(planned) + " races",
    racesPlanned: (n) => plural(n, "race", "races") + " on the calendar",
    raceThisWeekend: " · race this weekend",
    rulebookLink: (edition) => edition + " rulebook ↗",
    place: {
      map: (name2) => name2 + " · map (new tab)",
      mapLink: "Map ↗"
    },
    liveNow: "Live",
    inDays: (n) => "In " + plural(n, "day", "days"),
    clearAll: "Clear all",
    riders: (n) => plural(n, "rider", "riders"),
    standingsLink: "Standings →",
    allClasses: "← All classes",
    prevClasses: "Previous classes",
    nextClasses: "Next classes",
    leadBy: (n) => "+" + points(n) + " ahead",
    tiedLead: "Tied, ahead on the tiebreak",
    onlyRider: "Only rider",
    behind: (n) => points(n) + " behind",
    tied: "Tied",
    tieGap: "=",
    tieBy: {
      wins: (a, b) => "more wins (" + vs(a, b) + ")",
      places: (place2, a, b) => "more " + ordinal(place2) + " places (" + vs(a, b) + ")",
      outside: (a, b) => "more finishes out of the points (" + vs(a, b) + ")",
      last: (event) => "better result at the last final (" + event + ")",
      earlier: (event) => "better result at " + event,
      name: "alphabetical order"
    },
    tiedLeadBy: (reason) => "Tied, ahead on the tiebreak: " + reason,
    tieWhy: (reason) => "Tiebreak: " + reason,
    ofN: (n) => "of " + String(n),
    classFinal: "Final",
    classProgress: (done, planned) => String(done) + " / " + String(planned) + " races",
    plate: "No.",
    plateCaps: "NO.",
    pts: "pts",
    sortHint: "Tap a column header to sort.",
    columns: { rank: "#", num: "No.", name: "Rider", gross: "Gross", total: "Total", gap: "Gap" },
    tableLegend: {
      title: "Race by race",
      aside: "Points at each final",
      legendTitle: "Reading the table",
      intro: "One row per rider, one column per race: the points the rider scored in the final.",
      points: "Points scored in the final, from the scale: 25 for 1st, 22, 20… down to 1 pt for 20th (art. 19.2).",
      out: "Out of the points: beyond 20th place.",
      na: "No race for this class at this event.",
      gross: "Gross: the sum of every race. Total: after the Joker.",
      total: "Total: the sum of the points.",
      gap: 'Gap: points behind the leader; "=" when tied.'
    },
    noValue: "—",
    colon: ": ",
    // The title race (a class's panel, the cards' badges, the strip). The functions taking `who` return HTML:
    // `who` and the other text arguments must already be escaped.
    titleRace: {
      title: "Title race",
      badgeFinal: "★ Title",
      badgeSure: "Title clinched",
      badgeOpen: (n) => "Title open · " + String(n) + " in contention",
      decider: (race) => "It all comes down to " + race,
      racesLeft: (n) => String(n) + " races left",
      inDays: (n) => "in " + plural(n, "day", "days"),
      openLabel: (n) => n === 1 ? "title open" : "titles open",
      // The same word for 1 and more: this language doesn't need the number.
      sureLabel: () => "already clinched",
      lastRace: "Last race: ",
      nextIs: "Next: ",
      allSure: "Every title is clinched",
      stripGo: "What's at stake",
      racesLeftList: (n) => String(n) + " races left: ",
      final: (who2, title) => "<b>" + title + " title</b>: " + who2 + ". Final standings.",
      clinched: (who2, title, race) => "<b>" + title + " title clinched</b> by " + who2 + ": no one can catch their total anymore" + (race ? ", whatever happens at " + race : "") + ".",
      leads: (who2, n) => who2 + " leads. " + (n === 1 ? "1 rider can" : String(n) + " riders can") + " still catch their total.",
      entry: (who2, race) => who2 + " wins the title <b>just by entering " + race + "</b>, even without starting: the Joker would drop that result (art. 19.4, IMPORTANT 2). If absent, the title stays open.",
      win: (who2, race) => who2 + " wins the title <b>by winning at " + race + "</b>. Otherwise, it depends on the rivals.",
      place: (who2, place2, race) => who2 + " wins the title <b>by finishing " + place2 + " or better</b> at " + race + ", whatever the rivals do.",
      notEnough: (who2) => who2 + " leads, but even a win doesn't guarantee the title.",
      ifWin: (race) => "Title possible by winning at " + race,
      /** A rival's condition: the leader's results (leaderPlace, leaderOut, …) joined by orList. */
      ifLeaderFinishes: (leader, what) => "Title possible by winning, if " + leader + " " + what,
      ifLeaderNotRacing: (leader, what, race, withoutWin) => "Title possible if " + leader + " " + what + " at " + race + (withoutWin ? ", even without winning" : ", and by winning"),
      leaderPlace: (place2) => "finishes " + place2 + " or worse",
      leaderOut: "finishes out of the points",
      leaderDnf: "doesn't finish",
      /** DNS and absent both. */
      leaderNotRacing: "doesn't race",
      leaderAbsent: "doesn't show up",
      /** "a", "a or b", "a, b or c". */
      orList: (items) => items.length > 1 ? items.slice(0, -1).join(", ") + " or " + items[items.length - 1] : items.join(""),
      inContention: "In contention",
      max: "max",
      out: (n) => n === 1 ? "1 other rider can no longer win the title. " : String(n) + " other riders can no longer win the title. ",
      how: (joker) => "Computed with the points scale (art. 19.2), the tiebreak (art. 19.4)" + (joker ? " and the Joker" : "") + "; the rivals are assumed absent, the best case for each.",
      stakesTitle: (race) => "What's at stake at " + race,
      stakeLead: (leader, n) => leader + " leads · " + String(n) + " " + (n === 1 ? "rival" : "rivals") + " in contention",
      sureTitle: "Already decided",
      sureAside: (n) => plural(n, "title clinched", "titles clinched"),
      sureLead: (leader) => "★ " + leader + " · title clinched",
      needsEntry: "Title just by entering",
      needsWin: "Title by winning",
      needsPlace: (place2) => "Title by finishing " + place2 + " or better",
      needsNothing: "Nothing is decided",
      // A rider's objective.
      objChampion: "★ Champion · final standings",
      objSure: (race) => "★ Title clinched" + (race ? ", whatever happens at " + race : ""),
      objEntry: (race) => "Champion just by entering " + race,
      objWin: (race) => "Champion by winning at " + race,
      objPlace: (place2, race) => "Champion by finishing " + place2 + " or better at " + race,
      objNotEnough: (race) => "Leading, but even a win at " + race + " doesn't guarantee the title",
      objLeads: (races, rivals) => "Leading · " + String(races) + " races left · " + plural(rivals, "rival", "rivals") + " in contention",
      objContender: (max, races) => "In contention for the title · up to " + points(max) + " · " + String(races) + " races left",
      objOut: "Can no longer win the title"
    },
    // Following a rider.
    // Races.
    races: {
      results: "Results",
      resultsToCome: "Results to come",
      started: (n) => String(n) + " started",
      allResults: (n) => "All results (" + String(n) + ")",
      notStarted: (n) => n === 1 ? "1 non-starter (DNS)" : String(n) + " non-starters (DNS)",
      notRaced: (n) => n === 1 ? "1 rider of the class didn't race" : String(n) + " riders of the class didn't race",
      cancelled: (scale, n) => "Race not run for this class: " + points(scale) + " to each of the " + String(n) + " riders present.",
      later: "The results will show here after the race.",
      inDays: (n) => "In " + plural(n, "day", "days") + ". "
    },
    // An event's page (a CALENDAR row), and the links that open it.
    event: {
      eyebrow: (season) => "Event · " + season + " season",
      open: (event, when) => event + ", " + when + ": see the event",
      link: "See the event →",
      statStarters: "Riders who started",
      statClasses: "Classes raced",
      statChamps: "Championships",
      statTitles: "Titles decided",
      titlesHere: (event) => "Titles decided at " + event,
      titlesAside: "Tap a class for its standings",
      titlesCount: (n, final) => final ? plural(n, "champion crowned", "champions crowned") + " · final standings" : plural(n, "title clinched", "titles clinched") + " · no one can catch them anymore",
      winners: "Winners",
      results: "Results by class",
      resultsAside: "Tap a name for the rider's page",
      shared: (codes) => "Shared round " + codes + ": one final per class, in finishing order, with the same result in each championship (art. 13.2 IMPORTANT).",
      stakesCount: (n) => plural(n, "title open", "titles open"),
      noStakes: "No title is decided at this race.",
      byChamp: "By championship",
      byChampAside: "Each championship's standings and races",
      champStandings: (code) => code + " standings →"
    },
    // The Awards and their show (the Champions' Night).
    gala: {
      eyebrow: (s) => "Awards · " + s + " season",
      title: "Champions' Night",
      lede: (s) => "The champions, the podiums and the highlights of the " + s + " season.",
      play: "Start the show",
      playHint: "Full screen · Space, arrows or remote",
      privateNote: (when) => "Private preview: public on " + when,
      privateNoDate: "Private preview: no valid publication date in the GALA tab",
      quit: "Leave the private preview",
      // The form asking for the code, before PUBLIC.
      gateTitle: "Private preview",
      gateText: "For the organizers: enter the code to see the Awards before they are published.",
      gateLabel: "Code",
      gateOpen: "Open the preview",
      gateWrong: "Wrong code.",
      /** "Nov 14, 2026" and, with a time, " at 23:00". */
      when: (day, month, year, time) => MONTHS[month - 1] + " " + String(day) + ", " + year + (time ? " at " + time : ""),
      provisional: (codes) => "Season in progress: " + codes.join(", ") + (codes.length > 1 ? " have" : " has") + " races left. The champions shown are the current leaders.",
      factsTitle: "Highlights",
      factsAside: "Computed from the results",
      entry: (s) => s + " Awards",
      riders: (n) => plural(n, "rider", "riders"),
      lead: (n) => n === null ? "Only rider" : n === 0 ? "Tied, ahead on the tiebreak" : "+" + points(n) + " ahead",
      racesDone: (d, p) => d === p ? String(p) + " races" : "after " + String(d) + " of " + String(p) + " races",
      provisionalTag: (d, p) => "Provisional standings · after " + String(d) + " of " + String(p) + " races",
      final: "Final standings",
      title1: "★ Title",
      leading: "★ Leading",
      pts: " pts",
      ahead: "ahead of ",
      figures: {
        championships: "Championships",
        categories: "Classes",
        riders: "Riders",
        races: "Races run",
        finals: "Finals"
      },
      // The highlights.
      and: (n) => " and " + String(n) + " others",
      inChamps: (n) => "in " + plural(n, "championship", "championships"),
      titles: "Most titles",
      titlesUnit: "titles",
      wins: "Most wins",
      winsUnit: "finals won",
      winsDetail: "All classes. A shared round is one final.",
      podiums: "Most podiums",
      podiumsUnit: "podiums",
      podiumsDetail: "Top 3 in a final, all classes.",
      perfect: "Perfect season",
      perfectUnit: (n) => n === 1 ? "flawless class" : "flawless classes",
      perfectDetail: "Every final of the class won.",
      tight: "The closest title",
      tightUnit: (n) => n === 1 ? "pt gap" : "pts gap",
      tightTie: "Tied on points in the final standings: the title was decided by the tiebreak (art. 19.4).",
      tightGap: "Between the champion and 2nd in the final standings.",
      wide: "The biggest lead",
      wideUnit: "pts ahead",
      iron: "Never absent",
      ironUnit: "riders",
      ironDetail: "Took part in every final of their classes.",
      finals: (n) => plural(n, "final", "finals"),
      classes: "Most classes",
      classesUnit: "classes",
      classesDetail: (champs, entries) => "in " + plural(champs, "championship", "championships") + ", " + plural(entries, "standing", "standings") + ".",
      field: "The biggest field",
      fieldUnit: "riders",
      fieldDetail: "at the start of one final.",
      challenge: "Challenge",
      challengeNext: "Challenge · next season",
      goal: "Goal",
      trophy: "Trophy",
      trophyToWin: "Trophy to win",
      challenges: {
        TITLES: {
          goal: "2",
          unit: "titles",
          over: "Nobody has won two titles yet. The first double is still to be written: see you next season.",
          progress: "No rider has two titles in the bag yet. The last finals can change everything."
        },
        WINS: {
          goal: "1st",
          unit: "win",
          over: "No final was run. The first win is up for grabs next season.",
          progress: "The season is starting: the first final will open the count of wins."
        },
        PODIUMS: {
          goal: "1st",
          unit: "podium",
          over: "No final was run. The first podiums are up for grabs next season.",
          progress: "The season is starting: the first final will give the first podiums."
        },
        PERFECT: {
          goal: "100%",
          unit: "of the finals",
          over: "Nobody had a perfect season: every title had to be earned. Who will be the first to win every final of their class?",
          progress: "No finished class has a perfect season yet. The last finals can still bring one."
        },
        TIGHT: {
          goal: "1st",
          unit: "title decided",
          over: "No title was fought between two riders. The first title fight will be written next season.",
          progress: "No title is decided yet: the closest gap will be measured when the classes are over."
        },
        WIDE: {
          goal: "1st",
          unit: "title decided",
          over: "No title was fought between two riders. Who will build the biggest lead next season?",
          progress: "No title is decided yet: the biggest lead will be measured when the classes are over."
        },
        IRON: {
          goal: "100%",
          unit: "attendance",
          over: "The challenge is still open: take part in every final of your class. Who will take it on next season?",
          progress: "The challenge is on: take part in every final of your class, at least three. There are races left to take it on."
        },
        CLASSES: {
          goal: "2",
          unit: "classes",
          over: "Everyone raced a single class this season. Who will dare to race two next season?",
          progress: "Everyone races a single class for now. Who will dare to add a second?"
        },
        FIELD: {
          goal: "1st",
          unit: "final",
          over: "No final was run. The first starting grid will be written next season.",
          progress: "The season is starting: the first final will give the first start."
        }
      },
      details: "Details",
      detailsKey: "Details (I)",
      how: "How it is counted",
      howPick: "Explained by the organizers",
      explain: {
        TITLES: {
          text: "A title is finishing 1st in a class's standings at the end of the season. Each championship (CSM, SMQ…) has its own standings per class, so its own titles. This highlight rewards the rider who won the most titles this season.",
          example: "Example: 1st in the CSM's SM 5 standings and 1st in the SMQ's SM 5 standings = 2 titles."
        },
        WINS: {
          text: "A win is finishing 1st in a race. This highlight rewards the rider who won the most races this season, all classes together. A race that counts for two championships at once is counted once.",
          example: "Example: 1st in SM 5 at one race and 1st in SM 2 at another = 2 wins."
        },
        PODIUMS: {
          text: "A podium is finishing in the top 3 of a race. This highlight rewards the rider who stood on the podium most often this season, all classes together. A race that counts for two championships at once is counted once.",
          example: "Example: 1st, 3rd, then 2nd at three races = 3 podiums."
        },
        PERFECT: {
          text: "A perfect season is winning every race of your class, without losing a single one, against at least one opponent. The figure gives the number of classes where a rider had a perfect season.",
          example: "Example: 6 races in SM 3 in the SMQ, 6 wins by the same rider = 1 perfect season."
        },
        TIGHT: {
          text: "A title is finishing 1st in a class's standings at the end of the season. The closest title is the one won by the fewest points over the 2nd. On equal points, the rulebook separates the two riders ({rule}), and we say what made the difference.",
          example: "Example: 1st with 147 points, 2nd with 145 = a 2-point gap."
        },
        WIDE: {
          text: "The lead is the points gap between the 1st and the 2nd of a class's standings at the end of the season. This highlight shows the class where the 1st finished furthest ahead of the 2nd.",
          example: "Example: 1st with 165 points, 2nd with 119 = a 46-point lead."
        },
        IRON: {
          text: "Never absent is being there at every race of your classes this season (at least three). A retirement during the race (DNF) or a rider present who couldn't start (DNS) counts as present; an absence doesn't.",
          example: "Example: 7 races on the calendar of their class, present at all 7."
        },
        CLASSES: {
          text: "A class is a group of riders who race together (SM 1, SM 5, Lightweight…). This highlight rewards the rider who raced in the most different classes this season, all championships together.",
          example: "Example: a rider entered in SM 2 and in SM 5 = 2 classes."
        },
        FIELD: {
          text: "A race's start is the number of riders who set off (those who retired along the way count). This highlight shows the race that brought together the most riders this season, all classes together.",
          example: "Example: 25 riders at the start of the SM 10 race."
        }
      },
      ruleLink: "art. 19.4",
      // What separated two riders on equal points (art. 19.4, ranking.ts compareRulebook).
      why: {
        wins: (a, b) => "Separated by finals won: " + vs(a, b) + ".",
        places: (place2, a, b) => "As many finals won; separated by " + ordinal(place2) + " places: " + vs(a, b) + ".",
        outside: (a, b) => "As many wins, 2nd places, 3rds…; separated by the finishes out of the points: " + vs(a, b) + ".",
        last: (event, a, b) => "As many wins, 2nd places, 3rds…; separated by the last final (" + event + "): " + a + " vs " + b + ".",
        earlier: (event) => "Same places and same last final; separated by " + event + " (the rulebook stops at the last final: the site's rule).",
        name: "A perfect tie; separated by alphabetical order (the rulebook says nothing).",
        points: (n) => points(n),
        absent: "an absence (X)"
      },
      show: {
        label: "Champions' Night",
        brand: "Canadian Supermoto",
        open: "Champions' Night",
        openLabel: "Opening",
        group: "Season",
        openSeason: (s) => s + " season",
        keys: "Space or → to go on · ← to go back · M contents · F full screen · Esc to quit",
        touchKeys: "Tap the screen or swipe to go on · ☰ contents",
        statsLabel: "The season in figures",
        stats: (s) => "The " + s + " season in figures",
        of: (n) => "of " + String(n),
        champCounts: (classes, riders, races) => String(classes) + " classes · " + String(riders) + " riders · " + String(races) + " races",
        champsTitle: (s) => "Our " + s + " champions",
        leadersTitle: (s) => s + " leaders · provisional standings",
        giftsLabel: "Gifts",
        endLabel: "End",
        end: "Thank you!",
        endSub: (s) => "Congratulations to every rider of the " + s + " season. See you in " + String(Number(s) + 1) + ".",
        toc: "Contents",
        tocKey: "Contents (M)",
        closeToc: "Close the contents",
        tocHelp: "1 to 9: a championship · S: highlights · Home / End · A: 1 click/place ↔ Auto · Esc: close",
        tocStart: "Start",
        tocEnd: "End",
        tocScreens: (n) => plural(n, "screen", "screens"),
        tocChamp: "The championship's opening",
        rotate: "↻ Turn the device to enlarge",
        prev: "Previous screen",
        next: "Next screen",
        close: "Quit the show",
        closeKey: "Quit the show (Esc)",
        fullscreen: "Full screen (F)",
        modePlace: "1 click/place",
        modeAuto: "Auto",
        modeLabel: "Podiums: one click per place, or Auto, one click per class (A)",
        noName: "—",
        openers: {
          podiumsLabel: "The podiums",
          podiums: "The podiums",
          podiumsLine: (champs, podiums) => plural(champs, "championship", "championships") + " · " + plural(podiums, "podium", "podiums"),
          factsLine: (facts) => plural(facts, "highlight of the season", "highlights of the season"),
          championsLabel: "Our champions",
          champions: (s) => "Our " + s + " champions",
          championsLine: (titles, champs) => plural(titles, "title", "titles") + " · " + plural(champs, "championship", "championships"),
          leadersLabel: "Our leaders",
          leaders: (s) => "Our " + s + " leaders",
          leadersLine: (leaders, champs) => plural(leaders, "leader", "leaders") + " · " + plural(champs, "championship", "championships")
        },
        sponsors: {
          group: "Our partners",
          openLabel: "Thank you to our partners",
          title: ["Without them,", "nothing is possible"],
          sub: (partners, s) => plural(partners, "partner", "partners") + " · " + s + " season",
          tier: { PLATINUM: "Platinum", GOLD: "Gold", SILVER: "Silver", BRONZE: "Bronze", "A LA CARTE": "À la carte" },
          one: (tier) => tier + " partner",
          many: (tier) => tier + " partners",
          wallLabel: (tiers) => tiers.length ? tiers.join(" and ") : "Partners",
          wall: (tiers) => tiers.length ? tiers.join(" and ") + " partners" : "Our partners",
          since: (year) => "Partner since " + String(year),
          sinceLong: (year, seasons) => "Partner since " + String(year) + " · " + ordinal(seasons) + " season with us",
          firstSeason: "First season with us",
          loopLabel: "Partners (loop)",
          loopTitle: "Thank you to our partners"
        },
        promotions: {
          group: "Promotions",
          openLabel: "Promotions",
          sub: (riders) => plural(riders, "rider moves", "riders move") + " up a level",
          label: (discipline, from, to) => discipline + " · " + from + " → " + to,
          licence: (s) => "Promotion · Licence " + s,
          plate: (plates) => plates > 1 ? "Plates" : "Plate",
          level: "Level",
          rider: "Rider",
          levels: { AMATEUR: "Amateur", INTER: "Inter", PRO: "Pro" }
        },
        welcome: {
          label: "Welcome",
          title: "Welcome",
          sub: (s) => "to Champions' Night · " + s + " season",
          keys: "Space or → to start the show · M contents · F full screen · Esc to quit",
          touchKeys: "Tap the screen to start the show · ☰ contents",
          perClass: "riders per class",
          tour: (s) => "The " + s + " tour",
          tourSum: (races, km) => plural(races, "race", "races") + (km === null ? "" : " · " + num(km) + " km between the tracks"),
          race: (n, of) => "Race " + String(n) + " / " + String(of),
          soon: "Coming up",
          started: (riders, classes) => plural(riders, "rider", "riders") + " at the start · " + plural(classes, "class", "classes"),
          km: (n) => num(n) + " km",
          wall: "Thank you to the season's riders · find your name!",
          know: "Did you know?",
          facts: {
            plate: (no, riders) => [
              "No. " + no,
              "The most popular plate: " + String(riders) + " riders wear it"
            ],
            firstName: (name2, riders) => [
              name2,
              "The paddock's most common first name: " + String(riders) + " riders"
            ],
            lastName: (name2, riders) => [
              name2,
              "The most common last name: " + String(riders) + " riders"
            ],
            popular: (cls, champ, riders) => [
              cls,
              "The most raced class: " + plural(riders, "rider", "riders") + " (" + champ + ")"
            ],
            season: (start, end, days2, raceDays3) => [
              String(days2) + " days",
              "of season (" + dateRange({ start, end }) + "): " + plural(raceDays3, "race day", "race days")
            ],
            points: (n) => [num(n), "points given this season, all championships together"],
            month: (month, races) => [
              MONTHS_LONG[month - 1],
              "the season's busiest month: " + String(races) + " races"
            ],
            rest: (a, b, days2) => [
              String(days2) + " days",
              "the season's longest break, between " + a + " and " + b
            ],
            pair: (a, b, start, end) => [
              "Back to back",
              "two races in a row: " + (a === b ? a : a + " then " + b) + ", " + dateRange({ start, end })
            ],
            weekend: (venue, days2) => [String(days2) + " days", "the longest race weekend: " + venue],
            plates: (low, high) => [
              "No. " + String(low) + " → " + String(high),
              "the season's plates, from the lowest to the highest number"
            ],
            multi: (riders) => [
              num(riders),
              (riders === 1 ? "rider" : "riders") + " raced in more than one championship"
            ],
            host: (venue, races) => [String(races) + " ×", venue + " hosted the most races"]
          }
        }
      }
    },
    classLabel: "Class",
    noRiderFound: "No rider found",
    categories: (n) => plural(n, "class", "classes"),
    championships: (n) => plural(n, "championship", "championships"),
    // A rider's page.
    back: "← Back",
    riderEyebrow: (season) => "Rider · " + season + " season",
    copyLink: "Copy the link to this page",
    linkCopied: "Link copied",
    statStarts: "Races run",
    statWins: "Wins",
    statPodiums: "Podiums",
    statBest: (code, cls) => "Best rank · " + code + " " + cls,
    standingsTitle: "Standings",
    standingsAside: "In the championship, after the races run",
    outOf: (n) => "of " + plural(n, "rider", "riders"),
    leader: "Leader",
    leaderBy: (n) => "Leader · +" + points(n),
    leaderTied: (reason) => "Leader · tied, ahead on the tiebreak: " + reason,
    behindFirst: (n) => points(n) + " behind 1st",
    behindAhead: (n, rank) => " · " + String(n) + " behind " + ordinal(rank),
    classLink: (cls) => cls + " standings →",
    gridTitle: "Season results",
    gridAside: "Place in each final",
    gridHint: "Tap a cell for the race's details.",
    legendButton: "Legend",
    legendTitle: "Reading the grid",
    legendIntro: "One row per class, one column per race: the rider's place in the final.",
    legendPodium: "Podium: 1st, 2nd or 3rd place.",
    legendPlace: "Place in the final. It gives the points of the scale: 25, 22, 20, 18, 16, 15… down to 1 pt for 20th (art. 19.2).",
    legendOut: "Out of the points: beyond 20th place, 0 pts.",
    legendNotRun: "Race not run: the class didn't race, each rider present gets 25 pts.",
    legendDnf: "DNF: retired before the chequered flag (art. 5.67). DNS: didn't start. 0 pts.",
    legendAbsent: "Absent.",
    legendFuture: "Race to come.",
    legendJoker: "Joker: dropped from the total (art. 19.4).",
    legendCodes: "Under the class, the championships the rider races it in.",
    close: "Close",
    notRun: "pts · race not run",
    outOfPoints: "No pts",
    ptsOf: (n) => n === 1 ? "pt" : "pts",
    toCome: "to come",
    absent: "absent",
    joker: "Joker",
    // The Vérification page.
    mediaKit: {
      cardTitle: "Media kit",
      cardText: "Prepare the kit to hand to your sponsors: your photos, your texts, your packages.",
      button: "Prepare my media kit",
      back: "← Back to the rider",
      eyebrow: (s) => "Media kit · " + s,
      lead: "Fill in what you have, download your kit (.zip) and email it to the person who gave you access to this form. Your results come from the standings: nothing to type.",
      steps: ["Fill it in", "Download the .zip", "Email it"],
      resume: "Resume a kit (.zip)",
      sample: "See a sample kit (PDF)",
      stored: "Nothing is sent to the site: everything stays in your browser until you download it.",
      loading: "Loading the form…",
      loadError: "The form couldn’t be loaded. Check your connection, then reload the page.",
      codeTitle: "Your access code",
      codeText: "Enter the code you were given to open your media kit form.",
      codeLabel: "Code",
      codeOpen: "Open the form",
      codeWrong: "Wrong code.",
      codeLost: "Lost your code? Ask the person who gave you access to this form.",
      settingsTitle: "Kit settings",
      settingsAside: "For the whole kit",
      langsTitle: "Languages of the kit",
      langsSub: "Languages (one PDF per language)",
      langs: { fr: "French", en: "English", both: "Both" },
      langsNote: "You write every text yourself in each language chosen: it is printed as you typed it, never translated or corrected.",
      legendTitle: "Results’ legend",
      legend: "Show the legend under the results",
      legendHint: "The « Legend » block at the bottom of the Results page (podium, DNF, Joker…). Without it, the grid has more room.",
      preview: {
        open: "PDF preview",
        short: "Preview",
        title: "Kit preview",
        close: "Close",
        making: "Laying out…",
        pages: (n) => String(n) + (n > 1 ? " pages" : " page"),
        langs: { fr: "Français", en: "English" },
        zoom: "Page size",
        zoomFit: "Whole page",
        zoomReal: "Actual size",
        notice: "Preview: it may differ slightly from the final PDF.",
        more: (n) => "See what (" + String(n) + ")",
        lead: "The pages as the PDF will print them, with what you typed. Made again each time it opens.",
        results: (date) => "Results and figures as of " + date + ": the PDF takes those of the day the club makes it (one more race changes figures).",
        mark: "The « PREVIEW — NOT PAID » mark, the red band, your contact details and the QR code hidden: none of it in the final version.",
        browser: (name2) => "Your browser (" + name2 + ") isn’t the one that makes the PDF (Chrome). In our tests, the gap is 1 mm at most, with no line nor page different.",
        over: (n) => (n > 1 ? String(n) + " texts are" : "A text is") + " over the limit: the PDF can’t be made this way, fix it in the form.",
        engines: {
          ios: "the iPhone’s or iPad’s Safari engine",
          safari: "Safari",
          firefox: "Firefox",
          other: "this browser"
        }
      },
      inLang: { fr: "French", en: "English" },
      check: "Check",
      download: "Download my kit (.zip)",
      ok: "Everything is ready to download.",
      empty: (n) => String(n) + (n > 1 ? " empty fields: they will show « to come »." : " empty field: it will show « to come »."),
      over: (n) => (n > 1 ? String(n) + " texts too long: shorten them" : "1 text too long: shorten it") + " to download.",
      size: (mb) => "Size of the kit: " + mb + " MB",
      tooBig: "Over 18 MB: an email may refuse it. Pick lighter photos, or send the file with a sharing service (WeTransfer, Google Drive…).",
      making: "Preparing the kit…",
      reading: "Reading the kit…",
      badZip: "This file isn’t a media kit.",
      otherRider: (name2) => "This kit is « " + name2 + " »’s, not this rider’s.",
      onlyImages: "JPEG, PNG or WebP only.",
      duplicate: (place2) => "This image is already the « " + place2 + " » photo. Pick another image: the 6 photos must all be different.",
      error: (m) => "Error: " + m,
      doneTitle: "Last step: send your kit",
      doneText: (f) => "Your kit is downloaded: « " + f + " ». Attach this file to an email to the person who gave you access to this form.",
      doneKeep: "Keep the file: « Resume a kit (.zip) » reopens it to change it.",
      doneIphone: "On an iPhone: the file is in the Files app › Downloads; in Mail, tap « Attach File ».",
      doneAgain: "Change it again",
      clear: "Clear everything",
      clearTitle: "Clear everything?",
      clearText: (w) => "The texts and the photos of this form will be removed. A kit already downloaded (.zip) isn’t touched. To confirm, type " + w + " below.",
      clearWord: "CLEAR",
      clearLabel: "Word to type",
      clearYes: "Yes, clear everything",
      clearNo: "Cancel",
      cleared: "The form is empty: everything was cleared.",
      examples: {
        see: "✨ See an example",
        hide: "Hide the example",
        fromSeason: (s) => "Example from your " + s + " season: fill in the [brackets].",
        toComplete: "An example to complete: replace each [bracket] with your own words.",
        pick: "Pick a sentence, or write your own.",
        use: "Use this example",
        useOne: "Use",
        replace: "Replace your text with the example?",
        yes: "Yes, replace",
        no: "No",
        left: "Fill in this text's [brackets].",
        blocked: (n) => "Fill in the [brackets] of " + plural(n, "field", "fields") + " to download."
      },
      ai: {
        title: "Fill in with an AI",
        aside: "Optional",
        lead: "An AI asks you multiple-choice questions, one at a time: you answer with a letter. It then writes your texts in each language of the kit, at the right length. You read everything again before downloading.",
        step1: "Copy the text that tells the AI what to do. You'll then paste it into the AI.",
        copy: "Copy the text for the AI",
        copyAgain: "Copy again",
        nextTitle: "Text copied! Now:",
        next: [
          "Open an AI, whichever you like:",
          "Click in the box where you type a message, then paste: right-click › Paste (or Ctrl+V; on a Mac ⌘V; on a phone, long press › Paste).",
          "Send the message: the AI starts asking you its questions."
        ],
        seeText: "See the text for the AI",
        hideText: "Hide the text for the AI",
        copyFailed: "The text couldn't be copied: open « See the text for the AI », select all of it and copy it.",
        step2: "Answer its questions with a letter (A, B, C…). « Skip » leaves an answer empty.",
        step3: "At the end, it gives you a block of text with a « Copy » button. Click it, then paste here:",
        replyLabel: "The AI's answer",
        replyHolder: "{ … }",
        fill: "Fill in the form",
        privacy: "What you write to the AI is sent to it, under its own terms; this site sends nothing. Don't put your address, email or phone in it.",
        empty: "Paste the AI's answer first.",
        bad: "This answer can't be read. Copy the whole code block the AI gave you: it starts with { and ends with }.",
        otherRider: (name2) => "This answer is for « " + name2 + " », not for this rider.",
        nothing: "This answer holds no text to put in the form.",
        replaceQ: (n) => n > 1 ? "The answer replaces " + String(n) + " fields you have already filled in:" : "The answer replaces 1 field you have already filled in:",
        yes: "Yes, replace",
        keep: "No, keep my texts",
        cancel: "Cancel",
        done: (n) => (n > 1 ? String(n) + " fields filled in." : n === 1 ? "1 field filled in." : "No field changed.") + " Read each text again: it is printed as typed.",
        kept: (other) => "The texts in " + other + " are kept too: choose « Both » above to see them.",
        over: (n) => n > 1 ? String(n) + " texts too long, in red: shorten them, or ask the AI to shorten them." : "1 text too long, in red: shorten it, or ask the AI to shorten it.",
        langName: { fr: "French", en: "English" },
        prompt: {
          intro: "You're going to help me fill in my sponsorship kit as a supermoto rider (a form of the Canadian Supermoto website). The kit is meant to convince companies to sponsor me: a positive, concrete tone, without exaggerating.",
          knownH: "What the site already knows about me (it's true, don't change it)",
          rider: "Rider: ",
          season: (f) => f.season + " season: " + (f.starts ? plural(f.starts, "start", "starts") + (f.wins ? ", " + plural(f.wins, "win", "wins") : "") + (f.podiums ? ", " + plural(f.podiums, "podium", "podiums") : "") + (f.titles ? ", " + plural(f.titles, "title", "titles") : "") : "no race yet") + (f.classes.length ? "; classes: " + f.classes.join(", ") : "") + (f.champs.length ? "; championships: " + f.champs.join(", ") : "") + (f.best ? "; best standing: #" + String(f.best.rank) + " in " + f.best.cls + " (" + f.best.code + ")" : "") + ".",
          bikes: (codes) => "One bike per discipline: " + codes.join(", ") + ".",
          talkH: "How to talk to me",
          talk: [
            "I'm not a tech person: short sentences, simple words.",
            "Never use the words « JSON », « key », « field », « template » or « characters », nor the technical names (bio, hometown…): use the form's names, the ones between « » in the list below.",
            "Don't tell me about length limits: keep within them yourself.",
            "Never make up a result, a name, a figure or a fact about me: what you don't know, you ask me.",
            "Never ask for my address, email or phone."
          ],
          firstH: "How to ask me the questions",
          first: "Ask me multiple-choice questions, one at a time, and wait for my answer before the next one. Your first message starts right away, with nothing before it, with: « Hello! I'm going to ask you N short questions. Answer with a letter. », then question 1. Each question exactly in this shape (two examples):",
          shape: [
            "**Question 5 of 14 · Your short bio**",
            "Which one sounds most like you?",
            "A. « … »",
            "B. « … »",
            "C. « … »",
            "D. None: I'll write my own",
            "S. Skip",
            "Answer with a letter (e.g. B).",
            "",
            "**Question 9 of 14 · Your budget: tires**",
            "About how much do you spend on tires per season?",
            "A. $1,000",
            "B. $2,000",
            "C. $3,000",
            "D. Another amount: I'll write it",
            "S. Skip",
            "Answer with a letter (e.g. B)."
          ],
          rules: [
            "Always 2 to 4 choices A, B, C…, then an « Other » choice (« I'll write it ») and « S. Skip ». The only exception: what can't be guessed (see below).",
            "The texts you can write from my season (the bio, the quote, the goals): 3 different proposals, each in full, as choices A, B, C.",
            "What you can't know: plausible choices when possible (the first year: the last 3 years; the team: « None, I race on my own »; social media: Instagram, Facebook, TikTok, YouTube, with « several letters possible (e.g. A C) »; followers and amounts: typical round values). For the rest (town, age, account name, partners), a simple question: « Just write it (e.g. Sherbrooke), or S to skip. »",
            "The budget and the packages: one question per expense or package already in the template, for its amount or price.",
            "If I pick « Other », just ask: « Write your answer: ». If I write something other than a letter, understand it and go on.",
            "If the template already holds answers, your very first question is: « You have already filled part of the form. A. Only complete what is missing · B. Go over my answers one by one ». With A, ask nothing about what is already filled in. With B, one question per answer already given: « A. Keep mine: « … » », then 2 or 3 better versions (B, C, D), « Other » and « S. Skip ». If the template is empty, start right away.",
            "A text already filled in one language only: write the other language yourself, with the same meaning, without asking me.",
            "The kit's texts (« {pitch} », « {activation} », « {thanks} », « {noPartners} »): 3 proposals each, from my season, like the other texts; « S. Skip » lets the kit use its usual text.",
            "« {feats} »: ask whether I have an award, a race outside the club's standings or a first to mention (4 at most); for each, the year, a title and a sentence. Never fill « {source} » yourself: ask for a link only if I have one.",
            "« {past} »: my final results of the seasons before (the year, the class, the place, the championship), 10 at most. Ask for them: invent no result and no place.",
            "« {involvement} »: what I do for the sport off the track (day camp, coaching, volunteering), 3 lines at most. « {spoken} »: the languages I speak and my level, chosen from the lists given below.",
            "The questions in this order: you (town, age, first year, team, languages spoken), your bikes, your texts (bio, quote, goals), beyond the standings (highlights, earlier seasons, involvement), your social media, your budget, your packages, your partners, then the kit's texts. 20 questions at most: group what belongs together."
          ],
          langs: (page, other) => "Always write each text in French and in English, even if I picked only one language for my kit: I may change my mind. In your questions, show me only the " + page + "; the " + other + " you write yourself at the end, with the same meaning, said naturally (not translated word for word).",
          afterH: "After the last question",
          after: [
            "Show a short summary of my answers, numbered, then ask: « All good? A. Yes, give me the result · B. I want to change an answer (write its number) ».",
            "On « A », reply with this sentence: « It's ready! Click « Copy » at the top of the block below, paste it into the form (step 3), then click « Fill in the form ». », followed by one single json code block, in the shape of the template below. Nothing after the block. A skipped question stays empty.",
            "If I want to change something afterwards, give the whole block again, the same way."
          ],
          techH: "For you only (never show me this)",
          tech: [
            `The block: exactly the template's keys, nothing else, no comment. An empty text: "". An unknown number: null.`,
            "Amounts and prices in Canadian dollars: a whole number, no symbol (1500). Age, year and followers: a whole number.",
            "A text already filled in the template: copy it as is, unless I ask you to change it.",
            "spoken[].lang: one of fr (French), en (English), es (Spanish), it (Italian), pt (Portuguese), de (German), ar (Arabic), zh (Mandarin). spoken[].level: one of native, fluent, intermediate, basic. Any other language isn't possible: leave it out.",
            "Keep within each length below, spaces included: count them. The form refuses a text too long; shorter is better."
          ],
          mapH: "Each key, its name in the form and its length:",
          entry: (key, label2, length) => "- " + key + " → « " + label2 + " »: " + length,
          chars: (n) => String(n) + " characters at most",
          lines: (n, chars) => String(n) + " lines at most, " + String(chars) + " characters per line",
          rows: (n) => String(n) + " at most",
          number: "a whole number",
          model: "The template:"
        }
      },
      form: {
        sections: {
          photos: "Photos",
          identity: "Identity",
          bikes: "Bikes",
          words: "In the rider's words",
          beyond: "Beyond the standings",
          social: "Social networks",
          budget: "Season budget",
          packages: "Sponsorship packages",
          partners: "Current partners",
          contact: "Contact",
          texts: "The dossier's texts"
        },
        fields: {
          cover: "1 · couverture (cover)",
          portrait: "2 · portrait",
          profileLeft: "3 · profil-gauche (profile, left)",
          profileCenter: "4 · profil-centre (profile, centre)",
          profileRight: "5 · profil-droite (profile, right)",
          offer: "6 · forfaits (packages)",
          credit: "Photo credit",
          displayName: "Printed name",
          age: "Age",
          hometown: "Town or region",
          since: "Racing since (year)",
          team: "Team",
          bio: "Short bio",
          quote: "Quote",
          goals: "Next season's goals",
          feats: "Highlights",
          source: "Source",
          past: "Earlier seasons",
          involvement: "Involvement",
          spoken: "Languages spoken",
          network: "Network",
          handle: "Account",
          url: "Link",
          followers: "Followers",
          reach: "Average reach of a post",
          label: "Expense",
          amount: "Amount ($)",
          name: "Name",
          price: "Price ($)",
          benefits: "Benefits",
          logo: "Logo",
          email: "Email",
          phone: "Phone",
          pitch: "« The tour » introduction",
          activation: "Activation ideas",
          thanks: "Closing words (« Thank you » block)",
          noPartners: "Sentence when there is no partner"
        },
        hints: {
          cover: "Page 1 of the dossier, at the top. In action, landscape, 3000 px wide or more, the original file.",
          portrait: "The Profile page (p. 2), large on the left. Portrait orientation, in race gear.",
          profileLeft: "The Profile page (p. 2), bottom strip, left. Landscape.",
          profileCenter: "The Profile page (p. 2), bottom strip, centre. Landscape.",
          profileRight: "The Profile page (p. 2), bottom strip, right. Landscape.",
          offer: "The Packages page (p. 6), the wide bottom band. Landscape, very wide.",
          displayName: "As it will be printed, with accents and capitals: « Chelsea Parent-Boursier ».",
          hometown: "The rider's town or region. E.g. « Trois-Rivières, QC ».",
          bio: "3 or 4 sentences: the path, the drive, what sets the rider apart.",
          quote: "One strong sentence, in the rider's words. Shown large.",
          goals: "3 at most, one per line. E.g. « Aim for a podium in the Canadian championship ».",
          packages: "A proposal to adapt, optional: change the names and the benefits, enter your prices, add or remove packages.",
          budget: "Proposed expenses: enter your amounts and remove what doesn't apply. An expense without an amount isn't shown.",
          reach: "How many people see one of your posts, on average. Instagram: Professional dashboard › Insights › Accounts reached over 30 days, divided by the number of posts. Facebook: Meta Business Suite › Insights. E.g. « 1,200 people ».",
          texts: "Left empty, each field takes the default text, shown in grey.",
          credit: "The photographer's name, printed small on the cover. Optional.",
          age: "Optional. Shown next to the name.",
          since: "The year of the first race. E.g. 2016.",
          team: "The team's name, if there is one.",
          bike: "Make, model and displacement. E.g. « KTM 450 SMR ».",
          network: "E.g. Instagram, Facebook, TikTok, YouTube.",
          handle: "The account's name. E.g. @rider.name",
          url: "Optional. The link to the page.",
          followers: "The number of followers, no spaces. E.g. 1250",
          name: "The package's printed name. E.g. Platinum.",
          price: "The price in dollars, no sign. Leave it empty not to show it.",
          benefits: "What the sponsor gets, one benefit per line, 4 at most.",
          label: "The expense's name. E.g. Tires.",
          amount: "The amount in dollars for the season, no sign.",
          partnerName: "The current partner's name.",
          logo: "A transparent PNG or an SVG preferably.",
          contactName: "The person a sponsor should reach.",
          email: "The address that will receive the sponsors' replies.",
          phone: "Optional. E.g. 819 555-0100",
          pitch: "Replaces the introduction of « The tour » page.",
          activation: "What the rider offers to do for the sponsors, one per line, 4 at most.",
          thanks: "The closing words, under « Thank you », on the last page.",
          noPartners: "Shown only when no partner is listed."
        },
        intros: {
          photos: "Six photos, one per place in the dossier. Pick sharp originals; one image can be used only once.",
          identity: "What introduces the rider on the cover and the Profile page.",
          bikes: "One line per discipline raced this season, from the standings.",
          words: "The rider in their own words: what makes a sponsor want to follow them.",
          beyond: "What the official results don’t show. The dossier prints it on a page of its own, under « Information provided by the athlete ».",
          social: "The online audience, a strong argument for a sponsor.",
          packages: "Proposals to adapt: change them, add or remove some.",
          budget: "The season's expenses; an expense without an amount isn't shown.",
          partners: "The current sponsors, shown on the last page.",
          texts: "Default texts are already there (in grey).",
          contact: "The person who answers the sponsors."
        },
        rowNames: {
          social: "Network",
          packages: "Package",
          "budget.items": "Expense",
          partners: "Partner",
          bikes: "",
          feats: "Highlight",
          past: "Result",
          spoken: "Language"
        },
        add: "+ Add",
        remove: "Remove",
        upload: "Choose a file",
        replace: "Replace",
        drop: {
          photo: "Remove the photo",
          logo: "Remove the logo",
          askPhoto: "Remove this photo?",
          askLogo: "Remove this logo?",
          yes: "Yes, remove",
          no: "No"
        },
        beyond: {
          declared: "This information comes from you: the site can’t check it. The dossier keeps it apart from the official results, under « Information provided by the athlete ».",
          feats: "Highlights",
          featsHint: "An award, a race outside the club’s standings, a first… Up to 4, the latest first in the dossier.",
          addFeat: "+ Add a highlight",
          old: "From your former « Earlier seasons » field: check the year and the title, and add a sentence if you like.",
          year: "Year",
          source: "Source (optional)",
          sourceHint: "A link to the results, so that a sponsor can check.",
          title: "Title",
          titleHint: "60 characters at most.",
          detail: "In one sentence (optional)",
          detailHint: "160 characters at most. Without a sentence, the dossier prints the title alone.",
          past: "Earlier seasons",
          pastHint: "Your final results of the seasons before (the site has only the current one): one result per row, up to 10.",
          addResult: "+ Add a result",
          cls: "Class",
          rank: "Place",
          champ: "Championship",
          involvement: "Involvement",
          involvementHint: "What you do for the sport off the track: day camp, coaching, volunteering. Up to 3 lines.",
          spoken: "Languages spoken",
          spokenHint: "The dossier writes them itself in each language: « English (fluent) », « Anglais (courant) ».",
          lang: "Language",
          level: "Level",
          pickLang: "Choose the language…",
          pickLevel: "Choose the level…",
          addLang: "+ Add a language"
        },
        spoken: SPOKEN_EN,
        framing: {
          lead: "Each photo shows in the PDF’s exact frame: what you see here is what will be printed. Drag the photo inside its frame to choose what stays visible (keyboard: the arrows).",
          page: (n) => "Page " + String(n),
          centered: "Framed at the center",
          moved: "Framing adjusted",
          center: "Re-center",
          noMove: "This photo already has the frame’s shape: nothing to adjust.",
          label: (place2) => "Framing of the photo « " + place2 + " »: drag the photo or use the arrows"
        }
      },
      kit: {
        dec: (x) => x.toFixed(1),
        visibilityLead: (finals, weekends) => "With several classes on the programme, the bike is back on track final after final: " + finals + " finals per weekend on average, over " + String(weekends) + " race weekends. Every start is one more chance to show your brand.",
        activationIdeas: [
          "A thank-you post after every race weekend",
          "Photos and videos of the bike in your colours, free for your brand to use",
          "A visit or a showcase day at your business",
          "An end-of-season report: results, photos and exposure"
        ],
        thanksText: "Thank you for your interest. Let’s talk about how your brand can line up on the grid with us.",
        noPartners: "Your brand can be the first to back this season.",
        proposedPackages: [
          {
            name: "Platinum",
            benefits: [
              "Main logo on the bike",
              "Logo on the race suit",
              "A post after every race weekend",
              "End-of-season report"
            ]
          },
          {
            name: "Gold",
            benefits: ["Logo on the bike", "Named in the race posts", "End-of-season report"]
          },
          {
            name: "Silver",
            benefits: ["Logo on the trailer or the tent", "A thank-you on social media"]
          }
        ],
        proposedBudget: ["Entry fees", "Tires", "Travel and lodging", "Parts and upkeep", "Gear"],
        example: {
          bioStart: (name2) => name2 + " has raced supermoto since [first year].",
          bioSeason: (f, full) => {
            if (!f.classes.length) return "";
            const classes = full || f.classes.length <= 3 ? andList(f.classes) : String(f.classes.length) + " classes";
            if (!f.starts) return " In " + f.season + ": racing " + classes + ".";
            const champs = f.champs.length === 1 ? " in " + f.champs[0] : " across " + String(f.champs.length) + " championships" + (full || f.champs.length <= 3 ? " (" + f.champs.join(", ") + ")" : "");
            const won = [
              f.titles ? plural(f.titles, "title", "titles") : "",
              f.wins ? plural(f.wins, "win", "wins") : "",
              f.podiums ? plural(f.podiums, "podium", "podiums") : ""
            ].filter(Boolean);
            return " In " + f.season + ": " + plural(f.starts, "start", "starts") + " in " + classes + champs + (won.length ? ", with " + andList(won) : "") + ".";
          },
          bioEnd: ["[What got you into racing, in one sentence.]", "[What sets you apart on track.]"],
          quotes: [
            "Every start is a new chance to do better.",
            "I race to improve, one race at a time.",
            "On track, I give everything until the last lap."
          ],
          goals: (f, next) => [
            firstGoal(f, next),
            "Race the whole " + next + " season",
            "[A personal goal: better starts, moving up a level…]"
          ],
          reach: "[number] people per post"
        }
      }
    },
    check: {
      none: "No problems.",
      errors: (n) => plural(n, "error", "errors"),
      warnings: (n) => plural(n, "warning", "warnings"),
      errorsHelp: "To fix in the workbook: until then, what they touch isn't shown on the site.",
      warningsHelp: "To check: something looks wrong. The standings are shown.",
      general: "The whole tab",
      blockedTab: "This championship isn't shown until these errors are fixed.",
      blockedWorkbook: "Nothing is shown on the site until these errors are fixed.",
      workbook: "CONFIG and CALENDAR",
      gala: "“GALA” tab (Awards and Champions' Night)",
      highlights: "“HIGHLIGHTS” tab (highlights)",
      sponsors: "“SPONSORS” tab (partners)",
      distances: "“DISTANCES” tab (the show's map)",
      promotions: "“PROMOTIONS” tab (the riders who move up a level)",
      mediaKit: "“MEDIA KIT” tab (the riders offered the media kit's form)",
      season: (season) => "Season " + season,
      galaPart: {
        title: (season) => season + " gala",
        aside: "What the show shows, right now",
        help: (slides) => "The exact content of the Awards and the show at the moment this page is opened: " + plural(slides, "slide", "slides") + ". On gala night, read it again right after entering the last race: only names and figures can change, never the list.",
        podiums: "Podiums, in the show's order",
        champ: "Championship",
        classes: "Classes and champions",
        provisional: (done, total) => "Provisional · " + String(done) + " / " + String(total) + " races",
        updating: "being updated",
        facts: "Highlights, in the HIGHLIGHTS tab's order",
        head: { n: "#", fact: "Highlight", state: "State", now: "Content right now" },
        shown: "Shown",
        pick: "♥ Favourite",
        challenge: "Challenge",
        hidden: "Hidden",
        hiddenWhy: "SHOW = NO",
        off: "No highlight: the HIGHLIGHTS tab has errors for this season (see the Workbook tab).",
        sponsors: "Partners, in the show's order",
        sponsorsHead: { tier: "Tier", partners: "Partners" },
        noTier: "—",
        noLogo: "no logo: its name",
        sponsorsNone: "No partner: the show thanks no partner (see the Workbook tab).",
        promotions: "Promotions, in the PROMOTIONS tab's order",
        promotionsHead: { discipline: "Discipline", rider: "Rider", plates: "Plates", passage: "Promotion" },
        promotionsOff: "No promotion: the PROMOTIONS tab has errors for this season (see the Workbook tab)."
      },
      tab: (tab, code) => "“" + tab + "” tab (" + code + ")",
      tabNotShown: "This championship isn't shown: see the CONFIG errors.",
      between: "Between the tabs",
      noSeason: "No season in CONFIG."
    },
    // The dates, the places.
    dateRange,
    raceDays,
    /** "1<small>st</small>", "7<small>th</small>". */
    ord: (n) => String(n) + "<small>" + suffix(n) + "</small>",
    /** "1st", "7th". */
    ordText: (n) => ordinal(n),
    // The problems of the workbook.
    issuePlace,
    issues,
    cards,
    jokerHelp,
    /** `npm run validate:prod`'s label of a problem. */
    validate: { error: "ERROR", warning: "warning" },
    /**
     * The PHP's messages (plugin/languages/<lang>.php, written by `npm run
     * build`): printf formats, %s and %d as in PHP.
     */
    server: {
      lookMissing: "The CSM UI plugin (version %s or later) must be installed and activated.",
      noWorkbook: "No Google workbook ID was given (the shortcode's classeur attribute).",
      noAnswer: "Google Sheets didn't answer in time or couldn't be reached (%s). Try again in a moment.",
      googleBusy: "Google Sheets isn't answering right now (HTTP %d). Try again in a minute.",
      notShared: "Can't read the Google workbook (HTTP %d). Check that it is shared as “Anyone with the link can view”.",
      unreadable: "The workbook's file can't be read. Check that it is shared as “Anyone with the link can view”.",
      badEncoding: "The workbook holds unreadable text (character encoding): it can't be shown.",
      unavailable: "The standings are temporarily unavailable.",
      editorsOnly: "Only logged-in editors see these details.",
      checkTitle: "Workbook check",
      adminPrefix: "CSM Standings:"
    }
  };
  function ordinal(n) {
    return String(n) + suffix(n);
  }

  // plugins/csm-standings/src/site/i18n/fr.ts
  var plural2 = (n, one, many) => String(n) + " " + (n > 1 ? many : one);
  var SPOKEN_FR = {
    names: {
      fr: "Français",
      en: "Anglais",
      es: "Espagnol",
      it: "Italien",
      pt: "Portugais",
      de: "Allemand",
      ar: "Arabe",
      zh: "Mandarin"
    },
    levels: { native: "langue maternelle", fluent: "courant", intermediate: "intermédiaire", basic: "notions" }
  };
  var vs2 = (a, b) => String(a) + " contre " + String(b);
  var andList2 = (xs) => xs.length < 2 ? xs.join("") : xs.slice(0, -1).join(", ") + " et " + xs[xs.length - 1];
  function firstGoal2(f, next) {
    const b = f.best;
    if (!b) return "Viser [un podium / le titre] en " + (f.classes[0] ?? "[catégorie]") + " en " + next;
    const where = b.cls + " (" + b.code + ") en " + next;
    if (b.rank === 1 && b.final) return "Défendre le titre en " + where;
    return (b.rank <= 3 ? "Viser le titre en " : "Viser un podium en ") + where;
  }
  var MONTHS2 = ["janv.", "févr.", "mars", "avr.", "mai", "juin", "juil.", "août", "sept.", "oct.", "nov.", "déc."];
  var MONTHS_LONG2 = [
    "Janvier",
    "Février",
    "Mars",
    "Avril",
    "Mai",
    "Juin",
    "Juillet",
    "Août",
    "Septembre",
    "Octobre",
    "Novembre",
    "Décembre"
  ];
  var num2 = (n) => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, " ");
  function dateRange2(e) {
    const [, m1, d1] = e.start.split("-").map(Number);
    const [, m2, d2] = e.end.split("-").map(Number);
    if (e.start === e.end) return String(d1) + " " + MONTHS2[m1 - 1];
    if (m1 === m2) return String(d1) + "–" + String(d2) + " " + MONTHS2[m1 - 1];
    return String(d1) + " " + MONTHS2[m1 - 1] + " – " + String(d2) + " " + MONTHS2[m2 - 1];
  }
  function raceDays2(days2) {
    const parts = days2.map((d) => d.split("-").map(Number));
    const oneMonth = parts.every(([, m]) => m === parts[0][1]);
    const texts = parts.map(
      ([, m, d], i) => oneMonth && i < parts.length - 1 ? String(d) : String(d) + " " + MONTHS2[m - 1]
    );
    const last = texts.pop();
    return texts.length ? texts.join(", ") + " et " + last : last;
  }
  var quote2 = (s) => "« " + s + " »";
  var list2 = (values) => values.join(", ");
  var RACE_DAYS_FALLBACK2 = " (START–END) sous ses résultats.";
  var CLASSES_PLACE2 = (row2) => "CONFIG, ligne " + String(row2) + ", colonne SITE CLASS ORDER : ";
  var rows2 = (n) => (n.length > 1 ? "aux lignes " : "à la ligne ") + list2(n);
  function variants2(list3) {
    return list3.map((v) => quote2(v.name) + " (" + v.places.join(", ") + ")").join(" / ");
  }
  function issuePlace2(issue) {
    const parts = [];
    if (issue.row !== void 0) {
      const who2 = [issue.category, issue.rider].filter((s) => s !== void 0 && s !== "");
      parts.push("Ligne " + String(issue.row) + (who2.length ? " (" + who2.join(", ") + ")" : ""));
    } else if (issue.category !== void 0) {
      parts.push([issue.category, issue.rider].filter((s) => s !== void 0 && s !== "").join(", "));
    }
    if (issue.col !== void 0) {
      parts.push("colonne " + colLetter(issue.col) + (issue.column ? " (" + issue.column + ")" : ""));
    }
    return parts.length ? parts.join(", ") + " : " : "";
  }
  var issues2 = {
    "empty-sheet": () => "la feuille est vide. La ligne 1 doit contenir l'en-tête : CATEGORY, #, NAME, puis les épreuves.",
    "header-label": (issue) => "doit être " + quote2(issue.expected) + " (trouvé " + quote2(issue.found) + ").",
    "no-events": () => "l'en-tête doit contenir au moins une épreuve après NAME.",
    "event-empty": () => "le nom de l'épreuve est vide.",
    "event-reserved": (issue) => quote2(issue.found) + " ne peut pas être un nom d'épreuve.",
    "event-twice": (issue) => "l'épreuve " + quote2(issue.found) + " est déjà en colonne " + colLetter(issue.firstCol) + ". Chaque épreuve doit avoir un nom unique.",
    spaces: (issue) => quote2(issue.found) + " contient des espaces en trop (au début, à la fin ou deux de suite).",
    "outside-table": (issue) => quote2(issue.found) + " est à droite de la dernière épreuve : rien n'est permis à droite de l'en-tête.",
    "category-empty": () => "la catégorie est vide.",
    "name-empty": () => "le nom du pilote est vide.",
    "name-case": (issue) => quote2(issue.found) + " doit être écrit en majuscules.",
    "value-invalid": (issue) => quote2(issue.found) + " n'est pas permis. Valeurs permises : les points, DNF, DNS, X (absent), N/A (épreuve non tenue) ou vide (pas encore courue), en majuscules.",
    "na-mixed": (issue) => "N/A pour certains pilotes, mais pas " + rows2(issue.cells.map((c) => c.row)) + " (" + list2(issue.cells.map((c) => quote2(c.found))) + "). Si l'épreuve n'a pas eu lieu pour cette catégorie, tous les pilotes doivent avoir N/A ; sinon, remplacez N/A par le résultat.",
    "blank-mixed": (issue) => "vide " + rows2(issue.rows) + " alors que d'autres pilotes ont un résultat. Une fois l'épreuve courue, chaque pilote doit avoir une valeur : les points, DNF, DNS ou X (absent). Laissez toute la colonne vide seulement si l'épreuve n'a pas encore eu lieu.",
    "event-partial": (issue) => "résultats saisis pour " + list2(issue.entered) + ", pas encore pour " + list2(issue.pending) + ". Saisissez leurs résultats (ou N/A pour chaque pilote d'une catégorie qui ne l'a pas courue) : d'ici là, ces catégories sont affichées sans cette course, et elle ne compte pas encore comme courue.",
    "rider-twice": (issue) => quote2(issue.found) + " est déjà dans cette catégorie à la ligne " + String(issue.firstRow) + " (même nom, sans tenir compte des accents ni des majuscules). Fusionnez ou supprimez une des lignes.",
    "cancelled-race-value": (issue) => "plusieurs pilotes ont " + String(issue.top) + " points, donc la catégorie n'a pas couru cette course : seuls " + String(issue.top) + " et X sont permis (trouvé " + quote2(issue.found) + ").",
    "top-twice": (issue) => "la 1re place (" + String(issue.top) + " points) est donnée à " + String(issue.riders.length) + " pilotes : " + issue.riders.map((r) => "ligne " + String(r.row) + " (" + r.name + ")").join(", ") + ". Un seul pilote peut finir 1er (art. 5.67). Si la catégorie n'a pas couru cette course, mettez " + String(issue.top) + " à chaque pilote présent et X aux autres.",
    "points-off-scale": (issue) => quote2(issue.found) + " n'est pas dans le barème (" + list2(issue.scale) + " ou 0).",
    "points-twice": (issue) => "les mêmes points ont été donnés à plusieurs pilotes : " + list2(issue.points) + ".",
    "points-missing": (issue) => "aucun pilote n'a " + list2(issue.points) + " points alors que des places plus basses ont été données (pilote manquant ou erreur de saisie ?).",
    "zero-while-last-free": (issue) => "un pilote a 0 point alors que la dernière place payée (" + String(issue.last) + " pt) est libre.",
    "joker-class-missing": (issue) => "Joker : la catégorie " + quote2(issue.found) + " de la configuration n'existe pas dans la feuille.",
    "joker-event-missing": (issue) => "Joker : l'épreuve " + quote2(issue.found) + " de la configuration n'existe pas dans la feuille.",
    "number-format": (issue) => "le nombre " + quote2(issue.found) + " a le format " + quote2(issue.format) + ", que le site ne sait pas afficher exactement comme la feuille. Tant que la cellule n'est pas au format Automatique (ou Date, aaaa-mm-jj, pour une date), ce qu'elle concerne n'est pas affiché (la catégorie, le championnat ou la course).",
    "tab-missing": (issue) => "l'onglet " + quote2(issue.found) + " n'existe pas dans le classeur. Sans lui, rien ne peut être affiché : créez-le (nom exact, en majuscules).",
    "header-missing": (issue) => "l'en-tête " + quote2(issue.expected) + " est absent ou mal écrit (nom exact, en majuscules, sans espace en trop). Rien ne peut être affiché tant qu'il manque.",
    "cell-empty": () => "la cellule est vide.",
    "season-invalid": (issue) => quote2(issue.found) + " n'est pas une saison : écrivez l'année, par exemple 2026.",
    "order-invalid": (issue) => quote2(issue.found) + " n'est pas un ordre : écrivez un nombre entier (1, 2, 3…).",
    "order-twice": (issue) => "l'ordre " + quote2(issue.found) + " est déjà donné à la ligne " + String(issue.firstRow) + " pour la même saison. Chaque championnat doit avoir son propre ordre.",
    "championship-twice": (issue) => "le championnat " + quote2(issue.found) + " est déjà à la ligne " + String(issue.firstRow) + " pour la même saison. Cette ligne est ignorée.",
    "tab-not-found": (issue) => "l'onglet " + quote2(issue.found) + " n'existe pas dans le classeur (le nom doit être exactement celui de l'onglet).",
    "rules-missing": (issue) => "les règles " + issue.rulebook + " du " + issue.found + " ne sont pas encore programmées. Le championnat n'est pas affiché : demandez de les ajouter au site.",
    "url-invalid": (issue) => quote2(issue.found) + " n'est pas une adresse web (elle doit commencer par https://).",
    "color-invalid": (issue) => quote2(issue.found) + " n'est pas une couleur : écrivez-la en #RRGGBB, par exemple #D9302A.",
    "date-invalid": (issue) => quote2(issue.found) + " n'est pas une date : écrivez-la aaaa-mm-jj, par exemple 2026-05-15.",
    "dates-reversed": (issue) => "la fin (" + issue.end + ") est avant le début (" + issue.start + "). La course n'est pas affichée.",
    "championship-unknown": (issue) => quote2(issue.found) + " n'est pas un championnat de la saison " + issue.season + " dans l'onglet CONFIG (codes séparés par des virgules, par exemple « CSM, SMQ »).",
    "event-overlap": (issue) => "la course " + quote2(issue.found) + " est déjà à la ligne " + String(issue.firstRow) + " pour " + list2(issue.codes) + ". Une course ne peut être qu'une fois par championnat. Cette ligne est ignorée.",
    "race-days-empty": () => "les jours de course ne sont pas indiqués (colonne RACE DAYS : le ou les jours des finales, par exemple « 2026-09-19, 2026-09-20 »). Le site affiche la fin de semaine" + RACE_DAYS_FALLBACK2,
    "race-day-invalid": (issue) => "jours de course : " + quote2(issue.found) + " n'est pas une date (aaaa-mm-jj, séparées par des virgules, par exemple « 2026-09-19, 2026-09-20 »). Le site affiche la fin de semaine" + RACE_DAYS_FALLBACK2,
    "race-day-outside": (issue) => "jours de course : le " + issue.found + " n'est pas entre le début (" + issue.start + ") et la fin (" + issue.end + "). Le site affiche la fin de semaine" + RACE_DAYS_FALLBACK2,
    "race-days-order": (issue) => "jours de course : " + quote2(issue.found) + " doit donner chaque jour une seule fois, du premier au dernier. Le site affiche la fin de semaine" + RACE_DAYS_FALLBACK2,
    "language-unknown": (issue) => "le site n'a pas la langue " + quote2(issue.found) + " : rien ne lit cette colonne. Ses langues : " + list2(issue.languages.map((l) => l.toUpperCase())) + ".",
    "classes-missing": (issue) => CLASSES_PLACE2(issue.configRow) + "la catégorie " + quote2(issue.found) + " de l'onglet n'est pas dans la liste : elle est affichée après les autres. Ajoutez-la à sa place (nom exact).",
    "classes-twice": (issue) => CLASSES_PLACE2(issue.configRow) + quote2(issue.found) + " est plus d'une fois dans la liste : seule sa première place compte.",
    "season-twice": (issue) => "la saison " + quote2(issue.found) + " est déjà à la ligne " + String(issue.firstRow) + ". Cette ligne est ignorée.",
    "rulebook-unknown": (issue) => "le site ne connaît pas le règlement " + quote2(issue.found) + " (ses règles ne sont pas programmées) : les championnats de la saison ne sont pas affichés.",
    "season-unknown": (issue) => "la saison " + quote2(issue.found) + " n'est pas dans l'onglet SEASONS (la liste des saisons) : cette ligne est ignorée.",
    "venues-missing": () => "l'onglet « VENUES » n'est pas dans le classeur : les courses s'affichent sans leur lieu (nom exact, en majuscules).",
    "venues-header": (issue) => "l'en-tête " + quote2(issue.expected) + " manque ou est mal écrit (nom exact, en majuscules, sans espace en trop). Tant qu'il manque, les courses s'affichent sans leur lieu.",
    "venue-twice": (issue) => "le code " + quote2(issue.found) + " est déjà à la ligne " + String(issue.firstRow) + ". Cette ligne est ignorée.",
    "coordinates-invalid": (issue) => quote2(issue.found) + " n'est pas un point sur une carte : copiez-le depuis Google Maps, deux nombres séparés par une virgule, par exemple « 46.37123, -72.58034 ». Le lieu s'affiche sans son lien vers la carte.",
    "venue-unknown": (issue) => quote2(issue.found) + " n'est pas un CODE de l'onglet VENUES : la course s'affiche sans son lieu.",
    "gala-header": (issue) => "l'en-tête " + quote2(issue.expected) + " est absent ou mal écrit (SEASON, CODE, PUBLIC : noms exacts, en majuscules). Tant qu'il manque, aucune saison n'a de Palmarès.",
    "gala-season-unknown": (issue) => quote2(issue.found) + " n'est pas une saison de l'onglet SEASONS : cette ligne est ignorée.",
    "gala-season-twice": (issue) => "la saison " + quote2(issue.found) + " est déjà à la ligne " + String(issue.firstRow) + " : seule la première ligne compte. Supprimez celle-ci.",
    "gala-public-invalid": (issue) => (issue.found === "" ? "la date de publication est vide" : quote2(issue.found) + " n'est pas une date") + " : écrivez aaaa-mm-jj (public dès minuit) ou aaaa-mm-jj hh:mm, par exemple 2026-11-14 23:00. En attendant, le Palmarès ne devient jamais public tout seul ; le code l'ouvre quand même.",
    "gala-code-empty": () => "le code est vide : personne ne peut ouvrir le Palmarès avant la date de publication.",
    "highlights-missing": (issue) => "l'onglet HIGHLIGHTS n'existe pas, alors que l'onglet GALA a " + (issue.seasons.length > 1 ? "les saisons " : "la saison ") + issue.seasons.join(", ") + ". Créez-le : une ligne par fait saillant et par saison, les 9 faits calculés compris.",
    "highlights-header": (issue) => "l'en-tête " + quote2(issue.expected) + " est absent ou mal écrit (SEASON, CODE, TYPE, SHOW, VALUE, NAMES, puis TITLE, UNIT, DETAIL et EXPLANATION dans chaque langue du site : TITLE EN, TITLE FR… ; noms exacts, en majuscules).",
    "highlights-season-unknown": (issue) => quote2(issue.found) + " n'est pas une saison de l'onglet SEASONS : on ne sait pas à quelle saison cette ligne appartient.",
    "highlights-none": (issue) => "la saison " + issue.season + " est dans l'onglet GALA mais n'a aucune ligne dans HIGHLIGHTS. Chaque saison du gala liste ses faits saillants, dont les 9 faits calculés : " + HIGHLIGHT_CODES.join(", ") + ".",
    "highlight-twice": (issue) => quote2(issue.found) + " est déjà à la ligne " + String(issue.firstRow) + " pour cette saison : un fait saillant n'apparaît qu'une fois par saison.",
    "show-invalid": (issue) => (issue.found === "" ? "SHOW est vide" : quote2(issue.found) + " n'est ni YES ni NO") + " : écrivez YES pour afficher ce fait saillant, NO pour le masquer.",
    "trophy-invalid": (issue) => quote2(issue.found) + " n'est ni YES ni NO : écrivez YES si un trophée est remis pour ce fait saillant, sinon NO ou rien.",
    "type-invalid": (issue) => (issue.found === "" ? "TYPE est vide" : quote2(issue.found) + " n'est ni COMPUTED ni CUSTOM") + " : écrivez COMPUTED pour un fait que le site calcule (" + HIGHLIGHT_CODES.join(", ") + "), CUSTOM pour un fait de l'organisation.",
    "highlight-unknown": (issue) => issue.found + " n'est pas un fait que le site calcule : avec TYPE COMPUTED, le CODE doit être l'un des 9 (" + HIGHLIGHT_CODES.join(", ") + ").",
    "highlight-reserved": (issue) => issue.found + " est un fait que le site calcule : avec TYPE CUSTOM, il faut un autre CODE (" + HIGHLIGHT_CODES.join(", ") + " sont réservés au site).",
    "highlight-missing": (issue) => "la saison " + issue.season + " n'a pas de ligne pour " + issue.found + ". Chaque saison du gala liste les 9 faits calculés : " + HIGHLIGHT_CODES.join(", ") + ".",
    "highlight-computed-content": (issue) => issue.found + " est un fait calculé (TYPE COMPUTED) : le site écrit lui-même son chiffre, ses noms et ses textes. Cette case doit rester vide.",
    "highlight-value-invalid": (issue) => "VALUE est " + quote2(issue.found) + " : le grand chiffre doit être un nombre entier, sans espace ni unité (l'unité va dans UNIT).",
    "unit-without-value": (issue) => "l'unité est " + quote2(issue.found) + " mais VALUE est vide : un fait sans chiffre n'a pas d'unité, un fait avec un chiffre a les deux.",
    "highlight-incomplete": (issue) => issue.found + " est un fait de l'organisation (TYPE CUSTOM) : ces cases sont vides : " + issue.empty.join(", ") + ".",
    "distances-missing": (issue) => "il n'y a pas d'onglet DISTANCES, alors que " + (issue.seasons.length === 1 ? "la présentation de la saison " : "les présentations des saisons ") + issue.seasons.join(", ") + (issue.seasons.length === 1 ? " trace" : " tracent") + " la tournée sur une carte : créez-le, une ligne par paire de lieux (FROM, TO, KM : les km par la route).",
    "distances-header": (issue) => "l'en-tête " + quote2(issue.expected) + " est absent ou mal écrit (FROM, TO et KM ; noms exacts, en majuscules).",
    "distance-venue-unknown": (issue) => quote2(issue.found) + " n'est pas un CODE de l'onglet VENUES : cette ligne est ignorée.",
    "distance-same": (issue) => "FROM et TO valent tous deux " + quote2(issue.found) + " : une ligne relie deux lieux différents.",
    "distance-invalid": (issue) => "KM vaut " + quote2(issue.found) + " : écrivez les km par la route en nombre entier, chiffres seulement (35, pas « 35 km »).",
    "distance-twice": (issue) => issue.found + " est déjà à la ligne " + String(issue.firstRow) + " (une ligne sert dans les deux sens) : cette ligne est ignorée.",
    "distance-none": (issue) => "la carte de la présentation " + issue.season + " va de " + issue.from + " à " + issue.to + " (deux courses de suite), mais DISTANCES n'a pas de ligne pour ces deux lieux.",
    "promotions-header": (issue) => "l'en-tête " + quote2(issue.expected) + " manque ou est mal écrit (SEASON, NAME, DISCIPLINE, FROM et TO ; noms exacts, en majuscules).",
    "promotions-season-unknown": (issue) => quote2(issue.found) + " n'est pas une saison de l'onglet SEASONS : impossible de savoir à quelle saison appartient cette promotion.",
    "discipline-unknown": (issue) => quote2(issue.found) + " n'est pas une discipline du règlement de cette saison" + (issue.disciplines.length ? " (" + issue.disciplines.join(", ") + ", exactement)." : "."),
    "level-invalid": (issue) => quote2(issue.found) + " n'est pas un niveau de " + issue.discipline + " (" + issue.levels.join(", ") + ", exactement).",
    "promotion-not-up": (issue) => "FROM vaut " + issue.from + " et TO vaut " + issue.to + " : une promotion fait monter d'un niveau, jamais descendre ni rester au même (art. 1.7).",
    "promotion-twice": (issue) => quote2(issue.found) + " est déjà promu en " + issue.discipline + " à la ligne " + String(issue.firstRow) + " pour cette saison.",
    "promotion-name-unknown": (issue) => quote2(issue.found) + " n'est le NAME d'aucun pilote dans les onglets des championnats " + issue.season + ", écrit exactement pareil (majuscules et accents compris).",
    "promotion-no-discipline": (issue) => issue.found + " n'a aucune ligne dans une catégorie " + issue.discipline + " cette saison : la licence montre les plaques de toutes ses lignes.",
    "media-kit-header": (issue) => "l'en-tête " + quote2(issue.expected) + " manque ou est mal écrit (SEASON, NAME et CODE ; noms exacts, en majuscules).",
    "media-kit-season-unknown": (issue) => quote2(issue.found) + " n'est pas une saison de l'onglet SEASONS : impossible de savoir à quelle saison appartient cette ligne.",
    "media-kit-twice": (issue) => quote2(issue.found) + " est déjà inscrit à la ligne " + String(issue.firstRow) + " pour cette saison.",
    "media-kit-name-unknown": (issue) => quote2(issue.found) + " n'est le NAME d'aucun pilote dans les onglets des championnats " + issue.season + ", écrit exactement pareil (majuscules et accents compris) : pas de bouton du dossier de commandite.",
    "media-kit-code-empty": (issue) => issue.found + " n'a pas de CODE : sa fiche n'offre pas le dossier de commandite (le formulaire demande ce code avant de s'ouvrir).",
    "media-kit-code-twice": (issue) => issue.found + " a le même CODE que " + issue.other + " (ligne " + String(issue.firstRow) + ") : chacun pourrait ouvrir le formulaire de commandite de l'autre.",
    "sponsors-missing": (issue) => "il n'y a pas d'onglet SPONSORS, alors que l'onglet GALA a " + (issue.seasons.length === 1 ? "la saison " : "les saisons ") + issue.seasons.join(", ") + " : la présentation ne remercie aucun partenaire. Créez-le : une ligne par partenaire et par saison.",
    "sponsors-header": (issue) => "l'en-tête " + quote2(issue.expected) + " manque ou est mal écrit (SEASON, NAME et LOGO, puis TIER et SINCE si vous les utilisez ; noms exacts, en majuscules).",
    "sponsors-season-unknown": (issue) => quote2(issue.found) + " n'est pas une saison de l'onglet SEASONS : impossible de savoir à quelle saison ce partenaire appartient.",
    "sponsors-none": (issue) => "la saison " + issue.season + " est dans l'onglet GALA mais n'a aucun partenaire dans SPONSORS : sa présentation ne remercie aucun partenaire.",
    "sponsor-twice": (issue) => quote2(issue.found) + " est déjà à la ligne " + String(issue.firstRow) + " pour cette saison : un partenaire apparaît une fois par saison, cette ligne est donc ignorée.",
    "logo-unknown": (issue) => (issue.found === "" ? "LOGO est vide" : quote2(issue.found) + " n'est pas un des fichiers de logo du site") + " : la présentation écrit le nom du partenaire à la place de son logo.",
    "tier-invalid": (issue) => quote2(issue.found) + " n'est pas un niveau : écrivez PLATINUM, GOLD, SILVER, BRONZE ou A LA CARTE. D'ici là, la présentation le montre sur le mur, sans niveau.",
    "since-invalid": (issue) => "SINCE est " + quote2(issue.found) + " : écrivez une année (2019) ou laissez la case vide. D'ici là, la présentation ne dit pas depuis quand.",
    "since-after": (issue) => "SINCE est " + issue.found + ", après la saison " + issue.season + " : un partenaire de cette saison a commencé cette année-là ou avant. D'ici là, la présentation ne dit pas depuis quand.",
    "gala-order-header": (issue) => "l'en-tête " + quote2(issue.expected) + " est absent ou mal écrit (nom exact, en majuscules, sans espace en trop). Tant qu'il manque, la Soirée des champions présente les championnats dans l'ordre de SITE ORDER.",
    "gala-classes-header": (issue) => "l'en-tête " + quote2(issue.expected) + " est absent ou mal écrit (nom exact, en majuscules, sans espace en trop). Tant qu'il manque, la Soirée des champions présente les catégories de chaque championnat dans l'ordre de SITE CLASS ORDER.",
    "gala-classes-missing": (issue) => "la catégorie " + quote2(issue.found) + " des onglets de la saison n'est pas dans la liste : la Soirée des champions la présente après les autres. Ajoutez-la à sa place (nom exact).",
    "gala-classes-twice": (issue) => quote2(issue.found) + " est plus d'une fois dans la liste : seule sa première place compte.",
    "column-no-calendar": (issue) => "la colonne " + quote2(issue.found) + " de l'onglet " + quote2(issue.tab) + " n'a pas de ligne dans CALENDAR pour ce championnat (même nom de course, exactement).",
    "calendar-no-column": (issue) => "la course " + quote2(issue.found) + " de CALENDAR n'a pas de colonne dans l'onglet " + quote2(issue.tab) + " (même nom, exactement).",
    "column-order": (issue) => "les colonnes de l'onglet " + quote2(issue.tab) + " ne sont pas dans l'ordre des dates de CALENDAR (" + list2(issue.events) + " au lieu de " + list2(issue.expected) + "). Le bris d'égalité lit la dernière course (art. 19.4) : le championnat n'est pas affiché tant que l'ordre n'est pas corrigé.",
    "name-spelling": (issue) => "le même pilote est écrit de plusieurs façons : " + variants2(issue.variants) + ". Écrivez-le partout pareil.",
    "name-similar": (issue) => "probablement le même pilote : " + variants2(issue.variants) + ". Si c'est le cas, écrivez le même nom partout ; sinon, ignorez cet avertissement.",
    "class-spelling": (issue) => "la même catégorie est écrite de plusieurs façons : " + variants2(issue.variants) + ".",
    "event-spelling": (issue) => "la même course est écrite de plusieurs façons : " + variants2(issue.variants) + ".",
    "shared-outcome": (issue) => issue.event + " est une manche commune (une seule finale, art. 13.2 IMPORTANT), mais le résultat du pilote diffère : " + issue.values.map((v) => v.code + " " + quote2(v.found)).join(", ") + ". Mettez la même valeur dans chaque championnat de la manche. La catégorie n'est affichée dans aucun d'eux tant que ce n'est pas corrigé.",
    "shared-missing": (issue) => issue.event + " est une manche commune (une seule finale, art. 13.2 IMPORTANT) : le pilote a " + issue.values.map((v) => v.code + " " + quote2(v.found)).join(", ") + " mais aucune ligne dans l'onglet " + issue.missing.map((m) => m.code).join(", ") + ". Ajoutez sa ligne avec la même valeur, ou corrigez le nom s'il est écrit autrement. La catégorie n'est affichée dans aucun de ces championnats tant que ce n'est pas corrigé.",
    "shared-pending": (issue) => issue.event + " est une manche commune (une seule finale) : le résultat est saisi dans " + issue.values.filter((v) => v.found !== "").map((v) => v.code + " " + quote2(v.found)).join(", ") + " mais pas encore dans " + issue.values.filter((v) => v.found === "").map((v) => v.code).join(", ") + ". Saisissez-y la même valeur."
  };
  var and2 = (items) => items.length < 2 ? items.join("") : items.slice(0, -1).join(", ") + " et " + items[items.length - 1];
  var du = (codes) => and2(codes.map((c) => "du " + c));
  var au = (codes) => and2(codes.map((c) => "au " + c));
  function happened2(rider, value) {
    if (value === "0") return rider + " a terminé hors des points (0 pt)";
    if (/^\d+(\.\d+)?$/.test(value)) return rider + " a terminé la finale (" + value + " pts)";
    if (value === "DNF") return rider + " a pris le départ mais n'a pas terminé (DNF)";
    if (value === "DNS") return rider + " n'a pas pris le départ (DNS)";
    if (value === "X") return rider + " n'était pas à cette manche (X)";
    if (value === "N/A") return "La finale n'a pas eu lieu pour cette catégorie (N/A)";
    return quote2(value) + " est la bonne valeur";
  }
  var cards2 = {
    intro: "Chaque carte est un problème à corriger dans le classeur Google, avec les cellules exactes et les façons de le régler. Cochez « Réglé » au fur et à mesure : c'est un aide-mémoire, rien n'est enregistré. Rechargez la page pour revérifier le classeur.",
    doneCount: (done, total) => String(done) + (done > 1 ? " réglés" : " réglé") + " sur " + String(total),
    goTo: "Aller à",
    done: "Réglé",
    unfold: "Afficher",
    fold: "Masquer",
    error: "Erreur",
    warning: "Avertissement",
    errorsTitle: "À corriger : en attendant, ce qu'elles touchent n'est pas affiché sur le site",
    warningsTitle: "À vérifier : le classement est affiché",
    workbookPart: "Le classeur",
    workbookNav: "Classeur",
    seasonsTab: "Onglet « SEASONS »",
    venuesTab: "Onglet « VENUES »",
    configTab: "Onglet « CONFIG »",
    calendarTab: "Onglet « CALENDAR »",
    clean: "Sans problème :",
    allClean: "Aucun problème :",
    betweenHelp: "Les manches communes, les noms écrits de deux façons et les colonnes comparées au calendrier.",
    namesAndColumns: "Noms et colonnes",
    ref: (tab, col, row2) => tab + (col !== void 0 && col >= 0 ? ", " + colLetter(col) + (row2 !== void 0 ? String(row2) : "") : row2 !== void 0 ? ", ligne " + String(row2) : ""),
    head: { rider: "Pilote", cell: "Cellule", value: "Valeur", name: "Nom écrit", where: "Où", category: "Catégorie" },
    entered: "saisie",
    plate: (plate3) => "No " + plate3,
    noRow: "pas de ligne",
    blank: "vide",
    more: (n) => "Voir " + (n > 1 ? "les " + String(n) + " autres pilotes" : "l'autre pilote"),
    changes: (n) => "Voir " + (n > 1 ? "les " + String(n) + " cellules à changer" : "la cellule à changer"),
    change: (ref2, from, to) => ref2 + " : " + (from === "" ? "vide" : quote2(from)) + " → " + quote2(to),
    addRowAt: (tab, rider, value) => tab + " : ajoutez la ligne de " + rider + ", avec " + quote2(value),
    deleteRowAt: (ref2, rider) => ref2 + " : supprimez la ligne de " + rider,
    blocksTitle: "Ce que ça bloque",
    fixTitle: "Que faire ?",
    galaSeason: (season) => "Saison " + season,
    places: {
      title: "Les lieux",
      aside: (n) => plural2(n, "lieu", "lieux"),
      help: "Ouvrez chaque point une fois sur la carte : le site ne peut pas deviner un chiffre erroné (ce serait un autre endroit).",
      head: { code: "Code", name: "Nom", address: "Adresse", map: "Carte" },
      see: "Voir sur la carte ↗",
      noPoint: "Aucun point"
    },
    happened: happened2,
    titles: {
      "empty-sheet": "Onglet vide",
      "header-label": "En-tête mal écrit",
      "no-events": "Aucune course dans l'en-tête",
      "event-empty": "Course sans nom",
      "event-reserved": "Nom de course interdit",
      "event-twice": "Course en double",
      spaces: "Espaces en trop",
      "outside-table": "Quelque chose à droite du tableau",
      "category-empty": "Catégorie vide",
      "name-empty": "Nom du pilote vide",
      "name-case": "Nom pas en majuscules",
      "value-invalid": "Valeur non permise",
      "na-mixed": "N/A pour une partie des pilotes seulement",
      "blank-mixed": "Résultat manquant",
      "event-partial": "Course pas encore saisie pour toutes les catégories",
      "rider-twice": "Pilote en double",
      "cancelled-race-value": "Course non disputée : valeur non permise",
      "top-twice": "Plusieurs vainqueurs",
      "points-off-scale": "Points hors barème",
      "points-twice": "Mêmes points à plusieurs pilotes",
      "points-missing": "Une place sans pilote",
      "zero-while-last-free": "0 point alors qu'une place payée est libre",
      "joker-class-missing": "Joker : catégorie introuvable",
      "joker-event-missing": "Joker : course introuvable",
      "number-format": "Format de nombre",
      "tab-missing": "Onglet manquant",
      "header-missing": "En-tête manquant",
      "cell-empty": "Cellule vide",
      "season-invalid": "Saison illisible",
      "order-invalid": "Ordre illisible",
      "order-twice": "Même ordre pour deux championnats",
      "championship-twice": "Championnat en double",
      "tab-not-found": "Onglet introuvable",
      "rules-missing": "Règles pas encore programmées",
      "url-invalid": "Adresse web invalide",
      "color-invalid": "Couleur invalide",
      "date-invalid": "Date illisible",
      "dates-reversed": "Fin avant le début",
      "championship-unknown": "Championnat inconnu",
      "event-overlap": "Course en double",
      "race-days-empty": "Jours de course manquants",
      "race-day-invalid": "Jour de course illisible",
      "race-day-outside": "Jour de course hors de la fin de semaine",
      "race-days-order": "Jours de course dans le désordre",
      "language-unknown": "Colonne d'une langue inconnue",
      "classes-missing": "Catégorie absente de SITE CLASS ORDER",
      "classes-twice": "Catégorie deux fois dans SITE CLASS ORDER",
      "season-twice": "Saison en double",
      "rulebook-unknown": "Règlement pas encore programmé",
      "season-unknown": "Saison absente de la liste",
      "venues-missing": "Onglet manquant",
      "venues-header": "En-tête manquant",
      "venue-twice": "Lieu en double",
      "coordinates-invalid": "Point illisible",
      "venue-unknown": "Lieu inconnu",
      "gala-header": "En-tête manquant",
      "gala-season-unknown": "Saison inconnue",
      "gala-season-twice": "Saison en double",
      "gala-public-invalid": "Date de publication illisible",
      "gala-code-empty": "Code vide",
      "gala-order-header": "En-tête manquant",
      "gala-classes-header": "En-tête manquant",
      "gala-classes-missing": "Catégorie absente de GALA CLASS ORDER",
      "gala-classes-twice": "Catégorie deux fois dans GALA CLASS ORDER",
      "highlights-missing": "Onglet HIGHLIGHTS absent",
      "highlights-header": "En-tête manquant",
      "highlights-season-unknown": "Saison inconnue",
      "highlights-none": "Saison sans faits saillants",
      "highlight-twice": "Fait saillant en double",
      "show-invalid": "SHOW n'est ni YES ni NO",
      "trophy-invalid": "TROPHY n'est ni YES ni NO",
      "type-invalid": "TYPE n'est ni COMPUTED ni CUSTOM",
      "highlight-unknown": "Fait calculé inconnu",
      "highlight-reserved": "Code réservé au site",
      "highlight-missing": "Fait calculé absent de la saison",
      "highlight-computed-content": "Case à laisser vide",
      "highlight-value-invalid": "VALUE n'est pas un nombre entier",
      "highlight-incomplete": "Fait de l'organisation incomplet",
      "distances-missing": "Pas d'onglet DISTANCES",
      "distances-header": "En-tête manquant",
      "distance-venue-unknown": "Lieu inconnu",
      "distance-same": "Le même lieu deux fois",
      "distance-invalid": "KM n'est pas un nombre entier",
      "distance-twice": "Distance en double",
      "distance-none": "Distance manquante",
      "sponsors-missing": "Pas d'onglet SPONSORS",
      "promotions-header": "En-tête manquant",
      "promotions-season-unknown": "Saison inconnue",
      "discipline-unknown": "Discipline inconnue",
      "level-invalid": "Niveau inconnu",
      "promotion-not-up": "Pas une promotion",
      "promotion-twice": "Promotion en double",
      "promotion-name-unknown": "Pilote introuvable",
      "promotion-no-discipline": "Pilote hors de la discipline",
      "media-kit-header": "En-tête manquant",
      "media-kit-season-unknown": "Saison inconnue",
      "media-kit-twice": "Pilote inscrit deux fois",
      "media-kit-name-unknown": "Pilote introuvable",
      "media-kit-code-empty": "Code manquant",
      "media-kit-code-twice": "Même code deux fois",
      "sponsors-header": "En-tête manquant",
      "sponsors-season-unknown": "Saison inconnue",
      "sponsors-none": "Saison sans partenaires",
      "sponsor-twice": "Partenaire en double",
      "logo-unknown": "Logo inconnu",
      "tier-invalid": "Niveau inconnu",
      "since-invalid": "SINCE n'est pas une année",
      "since-after": "SINCE après la saison",
      "unit-without-value": "Unité sans chiffre",
      "column-no-calendar": "Colonne absente du calendrier",
      "calendar-no-column": "Course du calendrier sans colonne",
      "column-order": "Colonnes pas dans l'ordre des dates",
      "name-spelling": "Même pilote écrit de plusieurs façons",
      "name-similar": "Probablement le même pilote",
      "class-spelling": "Même catégorie écrite de plusieurs façons",
      "event-spelling": "Même course écrite de plusieurs façons",
      "shared-outcome": "Résultat différent d'un championnat à l'autre",
      "shared-missing": "Pilote absent d'un championnat de la manche",
      "shared-pending": "Résultat pas encore saisi partout"
    },
    shared: {
      outcome: (event) => event + " est une manche commune : une seule finale, donc le même résultat dans chaque championnat (art. 13.2 IMPORTANT). Ici, les cellules diffèrent.",
      missing: (event, codes) => event + " est une manche commune (art. 13.2 IMPORTANT) : le pilote a un résultat, mais aucune ligne dans sa catégorie " + au(codes) + ".",
      pending: (event) => event + " est une manche commune : le résultat est saisi dans un championnat, pas encore dans l'autre.",
      columnTitle: (event, category, codes, riders) => "La colonne " + event + " de " + category + " diffère entre " + and2(codes) + " pour " + plural2(riders, "pilote", "pilotes"),
      columnCause: (rider, sides) => {
        const results2 = sides.filter((s) => s.value !== null && s.value !== "X");
        const none = sides.filter((s) => s.value === null || s.value === "X");
        const said = (s) => (s.value === null ? "aucune ligne" : quote2(s.value)) + " " + au(s.codes);
        return "Cause probable : " + rider + " a " + and2(results2.map(said)) + ", mais " + and2(none.map(said)) + ". " + au(none.flatMap((s) => s.codes)).replace(/^au/, "Au") + ", les pilotes classés derrière ont sans doute été recomptés sans ce pilote : ils y ont une place de mieux. Une seule finale (art. 13.2 IMPORTANT) : il faut les mêmes cellules partout.";
      },
      columnNoCause: (event) => event + " est une manche commune : une seule finale, donc les mêmes cellules dans chaque championnat (art. 13.2 IMPORTANT). Plusieurs pilotes ont des cellules différentes.",
      pendingTitle: (event, category, empty) => "La colonne " + event + " de " + category + " n'est pas encore saisie " + au(empty),
      pendingColumn: (event, full, empty, riders) => event + " est une manche commune : " + plural2(riders, "résultat est saisi", "résultats sont saisis") + " " + au(full) + ", mais la colonne est encore vide " + au(empty) + ".",
      namesTitle: (a, b) => "Même pilote écrit " + quote2(a) + " et " + quote2(b) + " ?",
      names: (a, b, events) => quote2(a) + " et " + quote2(b) + " ont le même numéro (ou le même résultat) à " + and2(events) + ", chacun dans un seul des championnats de la manche : probablement le même pilote, écrit de deux façons. Tant que le nom diffère, le site les compte comme deux pilotes différents."
    },
    blocks: {
      nothing: "Rien : le classement est affiché.",
      partial: (event, pending) => "Rien : " + and2(pending) + (pending.length > 1 ? " sont affichées sans " : " est affichée sans ") + event + (pending.length > 1 ? " jusqu'à leur saisie" : " jusqu'à sa saisie") + " ; la course ne compte pas encore comme courue.",
      raceDays: "Rien : le site affiche la fin de semaine (START–END) au lieu des jours de course.",
      everything: "Tout le site : rien n'est affiché tant que ce n'est pas corrigé.",
      row: "Cette ligne est ignorée.",
      championship: (code, season) => "Le championnat " + code + " " + season + " n'est pas affiché.",
      race: (event, season) => "La course " + event + " " + season + " n'est pas affichée (ni dans le calendrier, ni dans les classements).",
      galaPublic: (season) => "Le Palmarès " + season + " ne devient jamais public tout seul (le code l'ouvre quand même).",
      galaNone: "Aucune saison n'a de Palmarès.",
      highlights: (seasons) => "Les faits saillants " + (seasons.length === 0 ? "de toutes les saisons" : (seasons.length > 1 ? "des saisons " : "de la saison ") + seasons.join(", ")) + " : aucun n'est affiché, ni sur la page Palmarès ni dans la présentation, tant que ce n'est pas corrigé. Rien des classements.",
      sponsorRow: (name2, season) => name2 + " n'est pas remercié dans la présentation " + season + " tant que ce n'est pas corrigé. Rien des classements.",
      distance: (from, to) => "Rien des classements : la carte de la présentation trace " + from + " – " + to + " sans ses km, et la tournée sans son total.",
      distancesAll: "Rien des classements : la carte de la présentation n'affiche aucun km tant que ce n'est pas corrigé.",
      sponsorsAll: "Aucun partenaire n'est remercié dans aucune présentation tant que ce n'est pas corrigé. Rien des classements.",
      promotions: (seasons) => (seasons.length === 0 ? "Les promotions de toutes les saisons" : (seasons.length > 1 ? "Les promotions des saisons " : "Les promotions de la saison ") + seasons.join(", ")) + " : aucune n'est annoncée dans la présentation tant que ce n'est pas corrigé. Rien des classements.",
      promotionPlates: (name2) => "Rien des classements : la licence de " + name2 + " dans la présentation a les plaques de toutes ses lignes.",
      mediaKit: (name2) => "Rien des classements : " + (name2 === "" ? "cette ligne n'offre" : "la fiche de " + name2 + " n'offre") + " aucun dossier de commandite tant que ce n'est pas corrigé.",
      mediaKitAll: "Rien des classements : aucune fiche de pilote n'offre le dossier de commandite tant que ce n'est pas corrigé.",
      noSponsors: (seasons) => "Rien des classements : la présentation " + (seasons.length === 1 ? "de la saison " : "des saisons ") + seasons.join(", ") + " ne remercie aucun partenaire.",
      logoName: (name2) => "Rien des classements : la présentation écrit " + name2 + " en grandes lettres à la place de son logo.",
      tierFallback: (name2) => "Rien des classements : la présentation montre " + name2 + " sur le mur, sans niveau.",
      sinceNone: (name2) => "Rien des classements : la présentation ne dit pas depuis quand " + name2 + " est partenaire.",
      showChamps: "Rien des classements : la Soirée des champions présente les championnats dans l'ordre de SITE ORDER.",
      showClasses: "Rien des classements : la Soirée des champions présente les catégories de chaque championnat dans l'ordre de SITE CLASS ORDER.",
      seasonRules: (season) => "Aucun championnat de la saison " + season + " n'est affiché.",
      places: "Rien des classements : les courses s'affichent sans leur lieu.",
      place: (event, season) => "Rien des classements : la course " + event + " " + season + " s'affiche sans son lieu.",
      mapLink: (code) => "Rien des classements : le lieu " + code + " s'affiche sans son lien vers la carte.",
      classIn: (category, codes) => category + (codes.length > 1 ? " n'est affichée ni " + codes.map((c) => "au " + c).join(", ni ") : " n'est pas affichée " + au(codes)) + "."
    },
    fix: {
      always: "Dans tous les cas",
      ignore: "rien à changer : cet avertissement ne bloque rien",
      wanted: "C'est voulu (décision de la Direction de course)",
      typo: "Une erreur de saisie",
      put: (value, refs) => "mettez " + quote2(value) + " en " + and2(refs),
      clear: (at2) => "videz " + at2,
      fill: (at2, header2) => "écrivez la valeur de " + header2 + " en " + at2,
      raced: (rider, codes) => rider + " a couru la finale et est inscrit " + au(codes),
      addRow: (tabs2, category, plate3, rider, event, col, value) => "ajoutez sa ligne dans " + and2(tabs2) + " : CATEGORY " + quote2(category) + ", # " + quote2(plate3) + ", NAME " + quote2(rider) + ", et " + quote2(value) + " dans la colonne " + col + " (" + event + ")",
      otherName: (rider, codes) => rider + " est déjà " + au(codes) + ", mais son nom y est écrit autrement",
      sameName: (rider) => "écrivez-y le nom exactement " + quote2(rider),
      didNotRace: (rider) => rider + " n'a pas couru cette finale",
      enterNow: "Le résultat est connu",
      sideRight: (codes) => "Les cellules " + du(codes) + " sont les bonnes",
      copyColumn: (event, category, from, to, cells) => "recopiez la colonne " + event + " de " + category + " " + du(from) + " dans " + and2(to.map((c) => "le " + c)) + " (" + plural2(cells, "cellule", "cellules") + ")",
      sameRider: (name2) => "C'est le même pilote, et son nom est " + quote2(name2),
      rename: (name2, refs) => "écrivez " + quote2(name2) + " en " + and2(refs),
      twoRiders: "Ce sont deux pilotes différents",
      addBoth: (names, events) => "ajoutez à chacun sa ligne dans l'autre championnat, avec le même résultat à " + and2(events) + " (" + and2(names) + ")",
      twoRidersSimilar: (names) => and2(names.map(quote2)) + " sont deux pilotes différents",
      writeHeader: (header2, tab) => "écrivez " + quote2(header2) + " dans la ligne 1 de l'onglet " + quote2(tab) + " (noms exacts, en majuscules)",
      addColumns: (tab) => "ajoutez les courses après NAME dans la ligne 1 de l'onglet " + quote2(tab) + ", une colonne chacune, dans l'ordre des dates",
      nameEvent: (at2) => "écrivez en " + at2 + " le nom de la course, exactement comme son EVENT dans CALENDAR",
      sameEvent: "C'est la même course deux fois",
      otherEvent: "Ce sont deux courses différentes",
      mergeColumns: (first, at2) => "gardez les résultats en " + first + " et supprimez la colonne de " + at2,
      meantPoints: (points2) => "Vous vouliez écrire " + points2 + " (des chiffres, pas des lettres)",
      meantSpecial: (value) => "Vous vouliez écrire " + value + " (en majuscules)",
      otherValue: "C'est autre chose",
      allowed: (at2) => "écrivez en " + at2 + " les points (en chiffres), DNF, DNS, X, N/A, ou laissez vide",
      notHeld: "L'épreuve n'a pas eu lieu pour cette catégorie",
      held: "L'épreuve a eu lieu pour cette catégorie",
      replaceNa: (column) => "remplacez chaque N/A de la colonne " + column + " par le résultat du pilote",
      enterResults: (refs) => "écrivez le résultat (points, DNF, DNS ou X) en " + and2(refs),
      enterClasses: (classes, column) => "écrivez les résultats de " + and2(classes) + " dans la colonne " + column,
      naForClass: (column) => "mettez N/A pour chacun de ses pilotes dans la colonne " + column,
      sameRiderTwice: "C'est le même pilote",
      mergeRows: (first, at2) => "gardez une seule ligne : mettez les résultats en " + first + ", puis supprimez " + at2,
      twoNamesakes: "Ce sont deux pilotes du même nom",
      tellApart: (at2) => "écrivez le nom autrement en " + at2 + " (un second prénom, une initiale…)",
      notRun: "La catégorie n'a pas couru cette course",
      run: "La catégorie a couru cette course",
      cancelled: (top2, refs) => "mettez " + String(top2) + " à chaque pilote présent et X aux autres en " + and2(refs),
      oneWinner: (top2, refs) => "gardez " + String(top2) + " pour le vainqueur seulement et donnez aux autres les points de leur place : " + and2(refs),
      scale: (at2, scale) => "écrivez en " + at2 + " des points du barème (" + scale.join(", ") + ") ou 0",
      samePoints: (points2, column) => "deux pilotes ne peuvent pas finir à la même place : corrigez l'un de ceux qui ont " + points2.join(", ") + " dans la colonne " + column,
      placeMissing: (points2) => "Un pilote a obtenu les " + String(points2) + " pts, mais ils ne sont pas saisis",
      givePoints: (points2, column) => "mettez " + String(points2) + " dans la cellule de ce pilote, colonne " + column,
      inThePoints: (last) => "Le pilote a fini à la dernière place payée (" + String(last) + " pt)",
      renamed: "Le nom a changé dans l'onglet",
      writeExactly: (name2, tab) => "écrivez exactement " + quote2(name2) + " dans l'onglet " + quote2(tab),
      jokerChange: "Les réglages du Joker doivent changer",
      askSite: "demandez de changer les réglages du Joker du site (ils sont dans le site, pas dans le classeur)",
      formatAuto: (at2) => "sélectionnez " + at2 + ", puis Format › Nombre › Automatique (ou Date, pour une date)",
      createTab: (name2) => "créez un onglet nommé exactement " + quote2(name2) + " (en-tête CATEGORY, #, NAME, puis les courses)",
      tabTypo: (name2) => "L'onglet existe sous le nom " + quote2(name2),
      tabToCreate: "L'onglet n'existe pas encore",
      writeSeason: (at2) => "écrivez l'année en " + at2 + ", par exemple 2026",
      writeOrder: (at2) => "écrivez en " + at2 + " un nombre entier qu'aucun autre championnat de la saison n'a (1, 2, 3…)",
      deleteRow: (row2, first) => "supprimez " + row2 + (first ? " (" + first + " compte)" : ""),
      editionTypo: "L'édition est mal écrite",
      writeEdition: (at2) => "écrivez en " + at2 + " l'édition telle que le site la connaît, par exemple 2026",
      newEdition: "C'est une nouvelle édition du livre de règlements",
      askEdition: (rulebook) => "demandez que le règlement " + rulebook + " soit ajouté au site (ajoutez son PDF au dépôt)",
      seasonNew: "La saison n'est pas encore dans SEASONS",
      addSeasonRow: (season) => "ajoutez sa ligne dans SEASONS : SEASON " + season + ", son RULEBOOK et son RULEBOOK URL",
      createSeasons: "créez un onglet nommé exactement « SEASONS » avec les en-têtes SEASON, RULEBOOK, RULEBOOK URL",
      createVenues: "créez un onglet nommé exactement « VENUES » avec les en-têtes CODE, NAME, ADDRESS, COORDINATES",
      venueTypo: "Le code est mal écrit",
      writeVenueCode: (at2) => "écrivez en " + at2 + " le CODE exact d'une ligne de VENUES",
      venueNew: "Le lieu n'est pas encore dans VENUES",
      addVenueRow: (code) => "ajoutez sa ligne dans VENUES : CODE " + quote2(code) + ", NAME, ADDRESS, COORDINATES",
      copyPoint: (at2) => "dans Google Maps, faites un clic droit sur l'endroit exact (un appui long sur un téléphone), cliquez sur les chiffres pour les copier, puis collez-les en " + at2,
      askRules: (rulebook, code) => "demandez d'ajouter au site les règles " + rulebook + " du " + code + " (ajoutez le PDF du livre de règlements au dépôt)",
      writeUrl: (at2) => "écrivez en " + at2 + " une adresse web complète, qui commence par https://",
      writeColor: (at2) => "écrivez en " + at2 + " une couleur en #RRGGBB, par exemple #D9302A",
      dateGuess: (date) => "La date est le " + date,
      writeDate: (at2) => "écrivez la date en " + at2 + " en aaaa-mm-jj, par exemple 2026-05-15",
      swapDates: (start, end) => "inversez " + start + " et " + end,
      codeTypo: "Le code est mal écrit",
      writeCode: (at2, season) => "écrivez en " + at2 + " le CODE exact d'une ligne " + season + " de CONFIG",
      codeMissing: "Le championnat n'est pas encore dans CONFIG",
      addConfigRow: (code, season) => "ajoutez sa ligne dans CONFIG : SEASON " + season + ", CODE " + quote2(code) + ", et le reste",
      raceDaysKnown: "Vous connaissez le ou les jours des finales",
      raceDays: (at2, start, end) => "écrivez-les en " + at2 + ", entre " + start + " et " + end + " (aaaa-mm-jj, séparés par des virgules : " + quote2(end) + ")",
      raceDaysUnknown: "Vous ne les connaissez pas encore",
      languageTypo: "Le code de la langue est mal écrit",
      writeLanguage: (at2, languages) => "écrivez en " + at2 + " l'en-tête avec une langue du site (" + languages.map((l) => l.toUpperCase()).join(", ") + ")",
      languageUnneeded: "La colonne ne sert pas",
      deleteColumn: (at2) => "supprimez la colonne de " + at2,
      addClass: (name2, at2) => "ajoutez " + quote2(name2) + " à la liste en " + at2 + ", à sa place",
      removeClass: (name2, at2) => "ne gardez " + quote2(name2) + " qu'une fois dans la liste en " + at2,
      seasonTypo: "La saison est mal écrite",
      rowUnneeded: "La ligne ne sert pas",
      writePublic: (at2) => "écrivez en " + at2 + " la date et l'heure où le Palmarès devient public, par exemple 2026-11-14 23:00",
      writeKitCode: (at2) => "écrivez en " + at2 + " le code que vous donnez à ce pilote : il le tape pour ouvrir le formulaire",
      changeKitCode: (at2, other) => "donnez un autre code à l'un des deux, en " + at2 + " ou en " + other,
      writeGalaCode: (at2) => "écrivez un code en " + at2 + " : les organisateurs le tapent pour voir le Palmarès avant",
      createHighlights: "créez l'onglet HIGHLIGHTS : en ligne 1, SEASON, CODE, TYPE, SHOW, VALUE, NAMES, puis TITLE, UNIT, DETAIL et EXPLANATION de chaque langue (TITLE EN, TITLE FR…) ; puis une ligne par fait saillant et par saison, dans l'ordre où ils doivent passer",
      addHighlights: (season) => "ajoutez les lignes de la saison " + season + " dans HIGHLIGHTS : les 9 faits calculés (" + HIGHLIGHT_CODES.join(", ") + "), chacun avec COMPUTED et YES ou NO, et vos faits à vous (CUSTOM), dans l'ordre où ils doivent passer",
      wantShown: "Vous voulez l'afficher",
      wantTrophy: "Un trophée est remis",
      noTrophy: "Aucun trophée n'est remis",
      wantHidden: "Vous ne voulez pas l'afficher",
      addHighlight: (season, code, show) => "ajoutez la ligne " + season + " · " + code + " · COMPUTED" + (show ? " · YES à l'endroit de l'onglet où il doit passer" : " · NO, n'importe où dans la saison"),
      sameHighlight: "C'est le même fait saillant",
      otherHighlight: "Ce sont deux faits différents",
      renameCode: (at2) => "donnez-lui un autre CODE en " + at2,
      writeShow: (at2) => "écrivez YES ou NO en " + at2,
      valueIs: (value) => "Le chiffre est " + value,
      writeValue: (at2) => "écrivez un nombre entier en " + at2 + ", sans espace ni unité",
      wasComputed: (code) => "C'était le fait calculé " + code,
      ownHighlight: "C'est un fait à vous",
      siteComputes: "Le site calcule ce fait",
      hasFigure: "Le fait a un chiffre",
      noFigure: "Le fait n'a pas de chiffre",
      makeCustom: (at2) => "écrivez CUSTOM en " + at2 + ", puis remplissez NAMES et les textes de chaque langue (VALUE et UNIT si le fait a un chiffre)",
      makeComputed: (at2) => "écrivez COMPUTED en " + at2 + " et videz VALUE, NAMES et les textes de la ligne",
      fillHighlight: (refs) => "remplissez " + and2(refs) + " : NAMES les noms, puis les textes de chaque langue (UNIT va avec VALUE)",
      writeOneOf: (at2, values) => "écrivez en " + at2 + " l'une des valeurs " + values.join(", ") + ", exactement",
      levelsSwapped: "FROM et TO sont inversés",
      swapLevels: (from, to) => "inversez " + from + " et " + to,
      notPromoted: "Le pilote n'est pas promu",
      riderIs: (name2) => "C'est " + name2,
      riderNotFound: (season) => "Le pilote a un autre nom dans les onglets " + season,
      checkName: (at2) => "écrivez en " + at2 + " le NAME exactement comme l'onglet du championnat l'écrit",
      disciplineWrong: "DISCIPLINE est erronée",
      checkDiscipline: (at2) => "corrigez " + at2,
      createDistances: "créez l'onglet DISTANCES : à la ligne 1, FROM, TO, KM ; puis une ligne par paire de lieux de VENUES (leur CODE), avec les km par la route que donne Google Maps",
      addDistance: (from, to) => "ajoutez une ligne dans DISTANCES : FROM " + from + ", TO " + to + ", KM les km par la route que donne Google Maps entre les deux lieux (une ligne sert dans les deux sens)",
      createSponsors: "créez l'onglet SPONSORS : à la ligne 1, SEASON, NAME, LOGO, TIER, SINCE (TIER et SINCE peuvent rester vides) ; puis une ligne par partenaire et par saison, dans l'ordre où ils doivent venir dans chaque niveau",
      addSponsors: (season) => "ajoutez les partenaires de la saison " + season + " dans SPONSORS, une ligne chacun (copiez les lignes de la saison d'avant et changez SEASON)",
      logoIs: (file) => "C'est le fichier " + file,
      logoNew: "C'est un nouveau logo",
      askLogo: (name2) => "demandez au développeur du site d'ajouter le logo de " + name2 + " au site, puis écrivez le nom de son fichier dans LOGO",
      writeTier: (at2) => "écrivez PLATINUM, GOLD, SILVER, BRONZE ou A LA CARTE en " + at2,
      sinceKnown: "Vous connaissez l'année où il a commencé",
      writeSince: (at2, season) => "écrivez-la en " + at2 + " (" + season + " ou avant)",
      sinceUnknown: "Vous ne la connaissez pas",
      calendarRowIgnored: (row2) => "CALENDAR a cette course à la ligne " + String(row2) + ", mais la ligne est ignorée à cause d'une erreur",
      seeCalendar: "corrigez-la (voir l'onglet « CALENDAR » plus haut) : cet avertissement disparaîtra",
      calendarRowMissing: "La course manque dans CALENDAR",
      addCalendarRow: (season, event, code) => "ajoutez une ligne dans CALENDAR : SEASON " + season + ", EVENT " + quote2(event) + ", CHAMPIONSHIPS " + quote2(code) + ", ses dates et son lieu",
      columnTypo: "Le nom de la colonne est mal écrit",
      renameColumn: (at2) => "écrivez en " + at2 + " le nom de la course exactement comme son EVENT dans CALENDAR",
      countsFor: (code) => "La course compte pour le " + code,
      addColumn: (event, tab) => "ajoutez la colonne " + quote2(event) + " dans " + tab + ", à sa place dans l'ordre des dates",
      notFor: (code) => "La course ne compte pas pour le " + code,
      removeCode: (code, at2) => "retirez " + quote2(code) + " de CHAMPIONSHIPS en " + at2,
      reorder: (tab, events) => "remettez les colonnes de " + tab + " dans l'ordre des dates : " + events.join(", "),
      rightName: (name2) => "Le bon nom est " + quote2(name2),
      writeIn: (name2, places) => "écrivez-le " + quote2(name2) + " aussi dans " + and2(places)
    }
  };
  var jokerHelp2 = {
    link: "Joker (art. 19.4)",
    article: "art. 19.4",
    dropped: "Joker : retiré du total",
    riderLine: (event, value, pts2) => "Joker : " + event + " (" + (pts2 > 0 ? String(pts2) + " pts" : value) + ") retiré",
    grossLine: (gross, pts2, net) => "Brut " + gross + (pts2 > 0 ? " − " + String(pts2) : "") + " = " + String(net) + " pts"
  };
  var fr = {
    // The page and the logo bar.
    eyebrow: "Canadian Supermoto",
    title: "Classements",
    seasonLabel: "Saison",
    seasonPick: (season) => "Saison " + season + ", changer",
    overviewTile: "VUE D'ENSEMBLE",
    barLabel: "Championnats",
    updating: "Classement en cours de mise à jour",
    /** The back-to-top button's name (CSM UI draws it). */
    toTop: "Haut de page",
    update: {
      title: "Nouvelle version du site",
      left: (time) => "Rechargement automatique dans " + time + ", ou à votre prochain clic.",
      live: (minutes) => "Nouvelle version du site. La page se rechargera dans " + String(minutes) + " minutes, ou à votre prochain clic.",
      reload: "Recharger",
      loading: "Mise à jour du site…",
      loadingSub: "Chargement de la nouvelle version, quelques secondes.",
      clock: (minutes, seconds) => String(minutes) + ":" + String(seconds).padStart(2, "0"),
      version: (name2, version) => name2 + " " + version,
      versions: (parts) => parts.join(" · ")
    },
    banner: {
      season: (season) => "Saison " + season,
      races: (done, total) => String(done) + " / " + String(total) + " courses disputées"
    },
    favs: {
      add: "Ajouter aux favoris",
      added: "Dans vos favoris",
      hint: "Vos favoris s'ouvrent d'un clic par ★ Favoris en haut de chaque page, sur cet appareil.",
      button: "Favoris",
      buttonAria: (n) => "Favoris (" + String(n) + ")",
      title: (n) => "Vos favoris" + (n > 1 ? " · " + String(n) : ""),
      empty: (button) => "Aucun favori pour l'instant. Sur la fiche d'un pilote, touchez " + button + ".",
      device: "Sur cet appareil seulement.",
      remove: (name2) => "Retirer " + name2 + " des favoris"
    },
    // The dashboard.
    nextRace: "Prochaine course",
    lastRace: "Dernière course",
    live: "● En cours",
    nextTag: "Prochaine",
    days: "jours",
    calendarTitle: (season) => "Calendrier " + season,
    calendarAside: "Glissez pour voir toute la saison",
    findRider: "Trouver un pilote",
    searchPlaceholder: "Trouver un pilote…",
    clearSearch: "Effacer la recherche",
    search: {
      head: "Pilotes",
      listLabel: "Pilotes trouvés",
      count: (n) => n === 0 ? "Aucun pilote trouvé" : n === 1 ? "1 pilote trouvé" : String(n) + " pilotes trouvés",
      more: (n) => "Précisez la recherche pour voir " + (n === 1 ? "l'autre pilote" : "les " + String(n) + " autres"),
      hint: "Tapez une partie du nom du pilote.",
      cancel: "Annuler"
    },
    leaders: "Meneurs par catégorie",
    leadersAside: "Touchez une catégorie pour son classement",
    leadersStakes: (race, open, sure) => "À " + race + " : " + [
      ...open ? [String(open) + (open > 1 ? " titres en jeu" : " titre en jeu")] : [],
      ...sure ? [String(sure) + (sure > 1 ? " déjà assurés" : " déjà assuré")] : []
    ].join(" · "),
    leadersSure: "Titre assuré",
    scheduleLabel: (event) => "Horaire " + event + " (nouvel onglet)",
    // A championship.
    finalStandings: "Classement final",
    /** The status pills (the tiles, the banner, the cards…). */
    phase: { final: "Final", live: "En cours", upcoming: "À venir", blocked: "Mise à jour" },
    seasonDone: "Terminée",
    /** A tile's pill while races remain: "En cours · 10/11". */
    tileLive: (done, total) => "En cours · " + String(done) + "/" + String(total),
    tileAria: (name2, status) => name2 + " — " + status,
    seasonAria: (season) => "Saison " + season,
    seasonStatus: {
      final: "terminée",
      live: (done, total) => "en cours, " + String(done) + " courses sur " + String(total),
      upcoming: "à venir",
      blocked: "classement en cours de mise à jour"
    },
    inProgress: (done, planned) => "après " + String(done) + " de " + String(planned) + " courses",
    racesPlanned: (n) => plural2(n, "course", "courses") + " au calendrier",
    raceThisWeekend: " · course ce week-end",
    rulebookLink: (edition) => "Règlements " + edition + " ↗",
    place: {
      map: (name2) => name2 + " · carte (nouvel onglet)",
      mapLink: "Carte ↗"
    },
    liveNow: "En cours",
    inDays: (n) => "Dans " + plural2(n, "jour", "jours"),
    clearAll: "Tout effacer",
    riders: (n) => plural2(n, "pilote", "pilotes"),
    standingsLink: "Classement →",
    allClasses: "← Toutes les catégories",
    prevClasses: "Catégories précédentes",
    nextClasses: "Catégories suivantes",
    leadBy: (n) => "+" + String(n) + " pts d'avance",
    tiedLead: "Égalité, devant au départage",
    onlyRider: "Seul pilote",
    behind: (n) => "à " + String(n) + " pts",
    tied: "Égalité",
    tieGap: "=",
    tieBy: {
      wins: (a, b) => "plus de victoires (" + vs2(a, b) + ")",
      places: (place2, a, b) => "plus de " + String(place2) + "es places (" + vs2(a, b) + ")",
      outside: (a, b) => "plus d'arrivées hors des points (" + vs2(a, b) + ")",
      last: (event) => "meilleur résultat à la dernière finale (" + event + ")",
      earlier: (event) => "meilleur résultat à " + event,
      name: "ordre alphabétique"
    },
    tiedLeadBy: (reason) => "Égalité, devant au départage : " + reason,
    tieWhy: (reason) => "Départage : " + reason,
    ofN: (n) => "sur " + String(n),
    classFinal: "Final",
    classProgress: (done, planned) => String(done) + " / " + String(planned) + " courses",
    plate: "No",
    plateCaps: "NO",
    pts: "pts",
    sortHint: "Touchez un en-tête de colonne pour trier.",
    columns: { rank: "#", num: "No.", name: "Pilote", gross: "Brut", total: "Total", gap: "Écart" },
    tableLegend: {
      title: "Course par course",
      aside: "Points à chaque finale",
      legendTitle: "Lire le tableau",
      intro: "Une ligne par pilote, une colonne par course : les points du pilote à la finale.",
      points: "Points obtenus à la finale, selon le barème : 25 au 1er, 22, 20… jusqu'à 1 pt au 20e (art. 19.2).",
      out: "Hors des points : au-delà de la 20e place.",
      na: "Pas de course pour cette catégorie à cette épreuve.",
      gross: "Brut : la somme de toutes les courses. Total : après le Joker.",
      total: "Total : la somme des points.",
      gap: "Écart : points de retard sur le meneur ; « = » à égalité."
    },
    noValue: "—",
    colon: " : ",
    // The title race (a class's panel, the cards' badges, the strip). The functions taking `who` return HTML:
    // `who` and the other text arguments must already be escaped.
    titleRace: {
      title: "Course au titre",
      badgeFinal: "★ Titre",
      badgeSure: "Titre assuré",
      badgeOpen: (n) => "Titre en jeu · " + String(n) + " en lice",
      decider: (race) => "Tout se joue à " + race,
      racesLeft: (n) => String(n) + " courses restantes",
      inDays: (n) => "dans " + plural2(n, "jour", "jours"),
      openLabel: (n) => n > 1 ? "titres en jeu" : "titre en jeu",
      sureLabel: (n) => n > 1 ? "déjà assurés" : "déjà assuré",
      lastRace: "Dernière course : ",
      nextIs: "Prochaine : ",
      allSure: "Tous les titres sont assurés",
      stripGo: "Ce qui se joue",
      racesLeftList: (n) => String(n) + " courses restantes : ",
      final: (who2, title) => "<b>Titre " + title + "</b> : " + who2 + ". Classement final.",
      clinched: (who2, title, race) => "<b>Titre " + title + " assuré</b> pour " + who2 + " : plus personne ne peut rattraper son total" + (race ? ", quoi qu'il arrive à " + race : "") + ".",
      leads: (who2, n) => who2 + " mène. " + (n > 1 ? String(n) + " pilotes peuvent" : "1 pilote peut") + " encore rattraper son total.",
      entry: (who2, race) => who2 + " remporte le titre <b>avec une simple inscription à " + race + "</b>, même sans prendre le départ : le Joker retirerait ce résultat (art. 19.4, IMPORTANT 2). En cas d'absence, le titre reste ouvert.",
      win: (who2, race) => who2 + " remporte le titre <b>en gagnant à " + race + "</b>. Sinon, tout dépend des rivaux.",
      place: (who2, place2, race) => who2 + " remporte le titre <b>en terminant " + place2 + " ou mieux</b> à " + race + ", quoi que fassent les rivaux.",
      notEnough: (who2) => who2 + " mène, mais même une victoire ne garantit pas le titre.",
      ifWin: (race) => "Titre possible en gagnant à " + race,
      /** A rival's condition: the leader's results (leaderPlace, leaderOut, …) joined by orList. */
      ifLeaderFinishes: (leader, what) => "Titre possible en gagnant, si " + leader + " " + what,
      ifLeaderNotRacing: (leader, what, race, withoutWin) => "Titre possible si " + leader + " " + what + " à " + race + (withoutWin ? ", même sans gagner" : ", et en gagnant"),
      leaderPlace: (place2) => "termine " + place2 + " ou moins bien",
      leaderOut: "termine hors des points",
      leaderDnf: "ne termine pas",
      /** DNS and absent both. */
      leaderNotRacing: "ne court pas",
      leaderAbsent: "ne se présente pas",
      /** "a", "a ou b", "a, b ou c". */
      orList: (items) => items.length > 1 ? items.slice(0, -1).join(", ") + " ou " + items[items.length - 1] : items.join(""),
      inContention: "En lice",
      max: "max",
      out: (n) => n > 1 ? String(n) + " autres pilotes ne peuvent plus remporter le titre. " : "1 autre pilote ne peut plus remporter le titre. ",
      how: (joker) => "Calculé avec le barème (art. 19.2), le départage (art. 19.4)" + (joker ? " et le Joker" : "") + " ; les rivaux sont supposés absents, le meilleur cas pour chacun.",
      stakesTitle: (race) => "Ce qui se joue à " + race,
      stakeLead: (leader, n) => leader + " mène · " + String(n) + " " + (n > 1 ? "rivaux" : "rival") + " en lice",
      sureTitle: "Déjà joué",
      sureAside: (n) => plural2(n, "titre assuré", "titres assurés"),
      sureLead: (leader) => "★ " + leader + " · titre assuré",
      needsEntry: "Titre avec une simple inscription",
      needsWin: "Titre en gagnant",
      needsPlace: (place2) => "Titre en terminant " + place2 + " ou mieux",
      needsNothing: "Rien n'est joué",
      // A rider's objective.
      objChampion: "★ Champion · classement final",
      objSure: (race) => "★ Titre assuré" + (race ? ", quoi qu'il arrive à " + race : ""),
      objEntry: (race) => "Champion avec une simple inscription à " + race,
      objWin: (race) => "Champion en gagnant à " + race,
      objPlace: (place2, race) => "Champion en terminant " + place2 + " ou mieux à " + race,
      objNotEnough: (race) => "En tête, mais même une victoire à " + race + " ne garantit pas le titre",
      objLeads: (races, rivals) => "En tête · " + String(races) + " courses restantes · " + plural2(rivals, "rival", "rivaux") + " en lice",
      objContender: (max, races) => "En lice pour le titre · jusqu'à " + String(max) + " pts · " + String(races) + " courses restantes",
      objOut: "Ne peut plus remporter le titre"
    },
    // Following a rider.
    // Courses.
    races: {
      results: "Résultats",
      resultsToCome: "Résultats à venir",
      started: (n) => String(n) + " au départ",
      allResults: (n) => "Tous les résultats (" + String(n) + ")",
      notStarted: (n) => n > 1 ? String(n) + " non-partants (DNS)" : "1 non-partant (DNS)",
      notRaced: (n) => n > 1 ? String(n) + " pilotes de la catégorie n'ont pas couru" : "1 pilote de la catégorie n'a pas couru",
      cancelled: (points2, n) => "Course non disputée pour cette catégorie : " + String(points2) + " pts à chacun des " + String(n) + " pilotes présents.",
      later: "Les résultats apparaîtront ici après la course.",
      inDays: (n) => "Dans " + plural2(n, "jour", "jours") + ". "
    },
    // An event's page (a CALENDAR row), and the links that open it.
    event: {
      eyebrow: (season) => "Évènement · saison " + season,
      open: (event, when) => event + ", " + when + " : voir l'évènement",
      link: "Voir l'évènement →",
      statStarters: "Pilotes au départ",
      statClasses: "Catégories courues",
      statChamps: "Championnats",
      statTitles: "Titres décidés",
      titlesHere: (event) => "Titres décidés à " + event,
      titlesAside: "Touchez une catégorie pour son classement",
      titlesCount: (n, final) => final ? plural2(n, "champion couronné", "champions couronnés") + " · classement final" : plural2(n, "titre assuré", "titres assurés") + " · plus personne ne peut les rattraper",
      winners: "Vainqueurs",
      results: "Résultats par catégorie",
      resultsAside: "Touchez un nom pour la fiche du pilote",
      shared: (codes) => "Ronde partagée " + codes + " : une seule finale par catégorie, dans l'ordre d'arrivée, avec le même résultat dans chaque championnat (art. 13.2 IMPORTANT).",
      stakesCount: (n) => plural2(n, "titre en jeu", "titres en jeu"),
      noStakes: "Aucun titre ne se décide à cette course.",
      byChamp: "Par championnat",
      byChampAside: "Classements et courses de chaque championnat",
      champStandings: (code) => "Classements du " + code + " →"
    },
    // The Palmarès and its show (the Soirée des champions).
    gala: {
      eyebrow: (s) => "Palmarès · saison " + s,
      title: "Soirée des champions",
      lede: (s) => "Les champions, les podiums et les faits saillants de la saison " + s + ".",
      play: "Lancer la présentation",
      playHint: "Plein écran · Espace, flèches ou télécommande",
      privateNote: (when) => "Aperçu privé : public le " + when,
      privateNoDate: "Aperçu privé : pas de date de publication valide dans l'onglet GALA",
      quit: "Quitter l'aperçu privé",
      // The form asking for the code, before PUBLIC.
      gateTitle: "Aperçu privé",
      gateText: "Réservé aux organisateurs : entrez le code pour voir le Palmarès avant sa publication.",
      gateLabel: "Code",
      gateOpen: "Ouvrir l'aperçu",
      gateWrong: "Code incorrect.",
      /** "14 nov. 2026" and, with a time, " à 23 h 00". */
      when: (day, month, year, time) => String(day) + " " + MONTHS2[month - 1] + " " + year + (time ? " à " + time.replace(":", " h ") : ""),
      provisional: (codes) => "Saison en cours : " + codes.join(", ") + (codes.length > 1 ? " ont" : " a") + " encore des courses. Les champions affichés sont les meneurs du moment.",
      factsTitle: "Faits saillants",
      factsAside: "Calculés à partir des résultats",
      entry: (s) => "Palmarès " + s,
      riders: (n) => String(n) + (n > 1 ? " pilotes" : " pilote"),
      lead: (n) => n === null ? "Seul pilote" : n === 0 ? "Égalité, devant au départage" : "+" + String(n) + " pts d'avance",
      racesDone: (d, p) => d === p ? String(p) + " courses" : "après " + String(d) + " de " + String(p) + " courses",
      provisionalTag: (d, p) => "Classement provisoire · après " + String(d) + " de " + String(p) + " courses",
      final: "Classement final",
      title1: "★ Titre",
      leading: "★ En tête",
      pts: " pts",
      ahead: "devant ",
      figures: {
        championships: "Championnats",
        categories: "Catégories",
        riders: "Pilotes",
        races: "Courses disputées",
        finals: "Finales"
      },
      // The highlights.
      and: (n) => " et " + String(n) + " autres",
      inChamps: (n) => "dans " + plural2(n, "championnat", "championnats"),
      titles: "Le plus de titres",
      titlesUnit: "titres",
      wins: "Le plus de victoires",
      winsUnit: "finales gagnées",
      winsDetail: "Toutes catégories. Une ronde partagée est une seule finale.",
      podiums: "Le plus de podiums",
      podiumsUnit: "podiums",
      podiumsDetail: "Top 3 à une finale, toutes catégories.",
      perfect: "Saison parfaite",
      perfectUnit: (n) => n > 1 ? "catégories sans-faute" : "catégorie sans-faute",
      perfectDetail: "Toutes les finales de la catégorie gagnées.",
      tight: "Le titre le plus serré",
      tightUnit: (n) => n > 1 ? "pts d'écart" : "pt d'écart",
      tightTie: "Égalité de points au classement final : le titre s'est joué au départage (art. 19.4).",
      tightGap: "Entre le champion et le 2e au classement final.",
      wide: "La plus grande avance",
      wideUnit: "pts d'avance",
      iron: "Jamais absents",
      ironUnit: "pilotes",
      ironDetail: "Ont pris part à toutes les finales de leurs catégories.",
      finals: (n) => plural2(n, "finale", "finales"),
      classes: "Le plus de catégories",
      classesUnit: "catégories",
      classesDetail: (champs, entries) => "dans " + plural2(champs, "championnat", "championnats") + ", " + plural2(entries, "classement", "classements") + ".",
      field: "Le plus gros départ",
      fieldUnit: "pilotes",
      fieldDetail: "au départ d'une même finale.",
      challenge: "Défi",
      challengeNext: "Défi · saison prochaine",
      goal: "Objectif",
      trophy: "Trophée",
      trophyToWin: "Trophée à gagner",
      challenges: {
        TITLES: {
          goal: "2",
          unit: "titres",
          over: "Personne n'a encore réuni deux titres. Le premier doublé reste à écrire : rendez-vous la saison prochaine.",
          progress: "Aucun pilote n'a encore deux titres en poche. Les dernières finales peuvent tout changer."
        },
        WINS: {
          goal: "1re",
          unit: "victoire",
          over: "Aucune finale n'a été courue. La première victoire est à prendre la saison prochaine.",
          progress: "La saison commence : la première finale ouvrira le compteur des victoires."
        },
        PODIUMS: {
          goal: "1er",
          unit: "podium",
          over: "Aucune finale n'a été courue. Les premiers podiums sont à prendre la saison prochaine.",
          progress: "La saison commence : la première finale donnera les premiers podiums."
        },
        PERFECT: {
          goal: "100 %",
          unit: "des finales",
          over: "Personne n'a signé de saison parfaite : chaque titre a dû se mériter. Qui sera le premier à remporter toutes les finales de sa catégorie ?",
          progress: "Aucune catégorie terminée n'a encore de saison parfaite. Les dernières finales peuvent encore en donner une."
        },
        TIGHT: {
          goal: "1er",
          unit: "titre décidé",
          over: "Aucun titre ne s'est joué entre deux pilotes. La première lutte au titre s'écrira la saison prochaine.",
          progress: "Aucun titre n'est encore décidé : l'écart le plus serré se mesurera quand les catégories seront terminées."
        },
        WIDE: {
          goal: "1er",
          unit: "titre décidé",
          over: "Aucun titre ne s'est joué entre deux pilotes. Qui creusera le plus grand écart la saison prochaine ?",
          progress: "Aucun titre n'est encore décidé : la plus grande avance se mesurera quand les catégories seront terminées."
        },
        IRON: {
          goal: "100 %",
          unit: "de présence",
          over: "Le défi reste ouvert : prendre part à toutes les finales de sa catégorie. Qui le relèvera la saison prochaine ?",
          progress: "Le défi est lancé : prendre part à toutes les finales de sa catégorie, au moins trois. Il reste des courses pour le relever."
        },
        CLASSES: {
          goal: "2",
          unit: "catégories",
          over: "Chacun a couru une seule catégorie cette saison. Qui osera en courir deux la saison prochaine ?",
          progress: "Chacun court une seule catégorie pour l'instant. Qui osera en ajouter une deuxième ?"
        },
        FIELD: {
          goal: "1re",
          unit: "finale",
          over: "Aucune finale n'a été courue. La première grille de départ s'écrira la saison prochaine.",
          progress: "La saison commence : la première finale donnera le premier départ."
        }
      },
      details: "Détails",
      detailsKey: "Détails (I)",
      how: "Comment c'est compté",
      howPick: "Expliqué par l'organisation",
      explain: {
        TITLES: {
          text: "Un titre, c'est terminer 1er du classement d'une catégorie à la fin de la saison. Chaque championnat (CSM, SMQ…) a son propre classement par catégorie, donc ses propres titres. Ce fait récompense le pilote qui a remporté le plus de titres cette saison.",
          example: "Exemple : 1er du classement SM 5 au CSM et 1er du classement SM 5 au SMQ = 2 titres."
        },
        WINS: {
          text: "Une victoire, c'est finir 1er d'une course. Ce fait récompense le pilote qui a gagné le plus de courses cette saison, toutes catégories confondues. Une course qui compte pour deux championnats en même temps n'est comptée qu'une fois.",
          example: "Exemple : 1er en SM 5 à une course et 1er en SM 2 à une autre = 2 victoires."
        },
        PODIUMS: {
          text: "Un podium, c'est finir dans les 3 premiers d'une course. Ce fait récompense le pilote qui est monté le plus souvent sur le podium cette saison, toutes catégories confondues. Une course qui compte pour deux championnats en même temps n'est comptée qu'une fois.",
          example: "Exemple : 1er, 3e puis 2e à trois courses = 3 podiums."
        },
        PERFECT: {
          text: "Une saison parfaite, c'est gagner toutes les courses de sa catégorie, sans en perdre une seule, face à au moins un adversaire. Le chiffre donne le nombre de catégories où un pilote a réussi une saison parfaite.",
          example: "Exemple : 6 courses en SM 3 au SMQ, 6 victoires du même pilote = 1 saison parfaite."
        },
        TIGHT: {
          text: "Un titre, c'est terminer 1er du classement d'une catégorie à la fin de la saison. Le titre le plus serré est celui qui a été gagné avec le moins de points d'avance sur le 2e. À égalité de points, le règlement départage les deux pilotes ({rule}), et on indique ce qui a fait la différence.",
          example: "Exemple : 1er à 147 points, 2e à 145 = 2 points d'écart."
        },
        WIDE: {
          text: "L'avance, c'est l'écart de points entre le 1er et le 2e du classement d'une catégorie à la fin de la saison. Ce fait montre la catégorie où le 1er a terminé le plus loin devant le 2e.",
          example: "Exemple : 1er à 165 points, 2e à 119 = 46 points d'avance."
        },
        IRON: {
          text: "Jamais absent, c'est être là à toutes les courses de ses catégories cette saison (au moins trois). Un abandon en course (DNF) ou un pilote présent qui n'a pas pu prendre le départ (DNS) compte comme une présence ; une absence, non.",
          example: "Exemple : 7 courses au calendrier de sa catégorie, présent aux 7."
        },
        CLASSES: {
          text: "Une catégorie, c'est un groupe de pilotes qui courent ensemble (SM 1, SM 5, Lightweight…). Ce fait récompense le pilote qui a couru dans le plus de catégories différentes cette saison, tous championnats confondus.",
          example: "Exemple : un pilote inscrit en SM 2 et en SM 5 = 2 catégories."
        },
        FIELD: {
          text: "Le départ d'une course, c'est le nombre de pilotes qui s'élancent (ceux qui ont abandonné en route comptent). Ce fait montre la course qui a réuni le plus de pilotes cette saison, toutes catégories confondues.",
          example: "Exemple : 25 pilotes au départ de la course SM 10."
        }
      },
      ruleLink: "art. 19.4",
      // What separated two riders on equal points (art. 19.4, ranking.ts compareRulebook).
      why: {
        wins: (a, b) => "Départagés par les finales gagnées : " + vs2(a, b) + ".",
        places: (place2, a, b) => "Autant de finales gagnées ; départagés par les " + String(place2) + "es places : " + vs2(a, b) + ".",
        outside: (a, b) => "Autant de victoires, de 2es places, de 3es… ; départagés par les arrivées hors des points : " + vs2(a, b) + ".",
        last: (event, a, b) => "Autant de victoires, de 2es places, de 3es… ; départagés par la dernière finale (" + event + ") : " + a + " contre " + b + ".",
        earlier: (event) => "Mêmes places et même dernière finale ; départagés par " + event + " (le règlement s'arrête à la dernière finale : règle du site).",
        name: "Égalité parfaite ; départagés par l'ordre alphabétique (le règlement ne prévoit rien).",
        points: (n) => String(n) + " pts",
        absent: "une absence (X)"
      },
      show: {
        label: "Soirée des champions",
        brand: "Canadian Supermoto",
        open: "Soirée des champions",
        openLabel: "Ouverture",
        group: "Saison",
        openSeason: (s) => "Saison " + s,
        keys: "Espace ou → pour avancer · ← pour revenir · M sommaire · F plein écran · Échap pour quitter",
        touchKeys: "Touchez l'écran ou balayez pour avancer · ☰ sommaire",
        statsLabel: "La saison en chiffres",
        stats: (s) => "La saison " + s + " en chiffres",
        of: (n) => "sur " + String(n),
        champCounts: (classes, riders, races) => String(classes) + " catégories · " + String(riders) + " pilotes · " + String(races) + " courses",
        champsTitle: (s) => "Nos champions " + s,
        leadersTitle: (s) => "Meneurs " + s + " · classement provisoire",
        giftsLabel: "Cadeaux",
        endLabel: "Fin",
        end: "Merci !",
        endSub: (s) => "Félicitations à tous les pilotes de la saison " + s + ". Rendez-vous en " + String(Number(s) + 1) + ".",
        toc: "Sommaire",
        tocKey: "Sommaire (M)",
        closeToc: "Fermer le sommaire",
        tocHelp: "1 à 9 : un championnat · S : faits saillants · Début / Fin · A : 1 clic/place ↔ Auto · Échap : fermer",
        tocStart: "Début",
        tocEnd: "Fin",
        tocScreens: (n) => plural2(n, "écran", "écrans"),
        tocChamp: "Présentation du championnat",
        rotate: "↻ Tournez l'appareil pour agrandir",
        prev: "Écran précédent",
        next: "Écran suivant",
        close: "Quitter la présentation",
        closeKey: "Quitter la présentation (Échap)",
        fullscreen: "Plein écran (F)",
        modePlace: "1 clic/place",
        modeAuto: "Auto",
        modeLabel: "Podiums : un clic par place, ou Auto, un clic par catégorie (A)",
        noName: "—",
        openers: {
          podiumsLabel: "Les podiums",
          podiums: "Les podiums",
          podiumsLine: (champs, podiums) => plural2(champs, "championnat", "championnats") + " · " + plural2(podiums, "podium", "podiums"),
          factsLine: (facts) => plural2(facts, "fait marquant de la saison", "faits marquants de la saison"),
          championsLabel: "Nos champions",
          champions: (s) => "Nos champions " + s,
          championsLine: (titles, champs) => plural2(titles, "titre", "titres") + " · " + plural2(champs, "championnat", "championnats"),
          leadersLabel: "Nos meneurs",
          leaders: (s) => "Nos meneurs " + s,
          leadersLine: (leaders, champs) => plural2(leaders, "meneur", "meneurs") + " · " + plural2(champs, "championnat", "championnats")
        },
        sponsors: {
          group: "Nos partenaires",
          openLabel: "Merci à nos partenaires",
          title: ["Sans eux,", "rien n'est possible"],
          sub: (partners, s) => plural2(partners, "partenaire", "partenaires") + " · Saison " + s,
          tier: { PLATINUM: "Platinum", GOLD: "Or", SILVER: "Argent", BRONZE: "Bronze", "A LA CARTE": "À la carte" },
          one: (tier) => "Partenaire " + tier,
          many: (tier) => "Partenaires " + tier,
          wallLabel: (tiers) => tiers.length ? tiers.join(" et ") : "Partenaires",
          wall: (tiers) => tiers.length ? "Partenaires " + tiers.join(" et ") : "Nos partenaires",
          since: (year) => "Partenaire depuis " + String(year),
          sinceLong: (year, seasons) => "Partenaire depuis " + String(year) + " · " + String(seasons) + "e saison avec nous",
          firstSeason: "Première saison avec nous",
          loopLabel: "Partenaires en boucle",
          loopTitle: "Merci à nos partenaires"
        },
        promotions: {
          group: "Promotions",
          openLabel: "Promotions",
          sub: (riders) => plural2(riders, "pilote monte", "pilotes montent") + " de niveau",
          label: (discipline, from, to) => discipline + " · " + from + " → " + to,
          licence: (s) => "Promotion · Licence " + s,
          plate: (plates) => plates > 1 ? "Plaques" : "Plaque",
          level: "Niveau",
          rider: "Pilote",
          levels: { AMATEUR: "Amateur", INTER: "Inter", PRO: "Pro" }
        },
        welcome: {
          label: "Accueil",
          title: "Bienvenue",
          sub: (s) => "à la Soirée des champions · Saison " + s,
          keys: "Espace ou → pour commencer la présentation · M sommaire · F plein écran · Échap pour quitter",
          touchKeys: "Touchez l'écran pour commencer la présentation · ☰ sommaire",
          perClass: "nombre de pilotes par catégorie",
          tour: (s) => "La tournée " + s,
          tourSum: (races, km) => plural2(races, "course", "courses") + (km === null ? "" : " · " + num2(km) + " km entre les pistes"),
          race: (n, of) => "Course " + String(n) + " / " + String(of),
          soon: "À venir",
          started: (riders, classes) => plural2(riders, "pilote", "pilotes") + " au départ · " + plural2(classes, "catégorie", "catégories"),
          km: (n) => num2(n) + " km",
          wall: "Merci aux pilotes de la saison · trouvez votre nom !",
          know: "Le saviez-vous ?",
          facts: {
            plate: (no, riders) => [
              "N° " + no,
              "Le numéro le plus populaire : " + String(riders) + " pilotes le portent"
            ],
            firstName: (name2, riders) => [
              name2,
              "Le prénom le plus courant du paddock : " + String(riders) + " pilotes"
            ],
            lastName: (name2, riders) => [
              name2,
              "Le nom de famille le plus répandu : " + String(riders) + " pilotes le portent"
            ],
            popular: (cls, champ, riders) => [
              cls,
              "La catégorie la plus courue : " + plural2(riders, "pilote", "pilotes") + " (" + champ + ")"
            ],
            season: (start, end, days2, raceDays3) => [
              String(days2) + " jours",
              "de saison (" + dateRange2({ start, end }) + ") : " + plural2(raceDays3, "jour", "jours") + " de course"
            ],
            points: (n) => [num2(n), "points distribués cette saison, tous championnats confondus"],
            month: (month, races) => [
              MONTHS_LONG2[month - 1],
              "le mois le plus chargé de la saison : " + String(races) + " courses"
            ],
            rest: (a, b, days2) => [
              String(days2) + " jours",
              "le plus long répit de la saison, entre " + a + " et " + b
            ],
            pair: (a, b, start, end) => [
              "Coup sur coup",
              "deux courses de suite : " + (a === b ? a : a + " puis " + b) + ", " + dateRange2({ start, end })
            ],
            weekend: (venue, days2) => [
              String(days2) + " jours",
              "la plus longue fin de semaine de course : " + venue
            ],
            plates: (low, high) => [
              "N° " + String(low) + " → " + String(high),
              "les plaques de la saison, du plus petit au plus grand numéro"
            ],
            multi: (riders) => [
              num2(riders),
              (riders > 1 ? "pilotes ont couru" : "pilote a couru") + " dans plus d'un championnat"
            ],
            host: (venue, races) => [String(races) + " ×", venue + " a reçu le plus de courses"]
          }
        }
      }
    },
    classLabel: "Catégorie",
    noRiderFound: "Aucun pilote trouvé",
    categories: (n) => plural2(n, "catégorie", "catégories"),
    championships: (n) => plural2(n, "championnat", "championnats"),
    // A rider's page.
    back: "← Retour",
    riderEyebrow: (season) => "Fiche pilote · saison " + season,
    copyLink: "Copier le lien de la fiche",
    linkCopied: "Lien copié",
    statStarts: "Courses disputées",
    statWins: "Victoires",
    statPodiums: "Podiums",
    statBest: (code, cls) => "Meilleur rang · " + code + " " + cls,
    standingsTitle: "Classements",
    standingsAside: "Au championnat, après les courses disputées",
    outOf: (n) => "sur " + plural2(n, "pilote", "pilotes"),
    leader: "Meneur",
    leaderBy: (n) => "Meneur · +" + String(n) + " pts",
    leaderTied: (reason) => "Meneur · à égalité, devant au départage : " + reason,
    behindFirst: (n) => String(n) + " pts du 1er",
    behindAhead: (n, rank) => " · " + String(n) + " du " + String(rank) + "e",
    classLink: (cls) => "Classement " + cls + " →",
    gridTitle: "Résultats de la saison",
    gridAside: "Place à chaque finale",
    gridHint: "Touchez une case pour le détail de la course.",
    legendButton: "Légende",
    legendTitle: "Lire la grille",
    legendIntro: "Une ligne par catégorie, une colonne par course : la place du pilote à la finale.",
    legendPodium: "Podium : 1re, 2e ou 3e place.",
    legendPlace: "Place à la finale. Elle donne les points du barème : 25, 22, 20, 18, 16, 15… jusqu'à 1 pt au 20e (art. 19.2).",
    legendOut: "Hors des points : au-delà de la 20e place, 0 pt.",
    legendNotRun: "Course non disputée : la catégorie n'a pas couru, chaque pilote présent reçoit 25 pts.",
    legendDnf: "DNF : abandon avant le drapeau à damier (art. 5.67). DNS : non-partant. 0 pt.",
    legendAbsent: "Absent.",
    legendFuture: "Course à venir.",
    legendJoker: "Joker : retiré du total (art. 19.4).",
    legendCodes: "Sous la catégorie, les championnats où le pilote y court.",
    close: "Fermer",
    notRun: "pts · course non disputée",
    outOfPoints: "Hors pts",
    ptsOf: (n) => n > 1 ? "pts" : "pt",
    toCome: "à venir",
    absent: "absent",
    joker: "Joker",
    // The Vérification page.
    mediaKit: {
      cardTitle: "Dossier de commandite",
      cardText: "Préparez le dossier à remettre à vos commanditaires : vos photos, vos textes, vos forfaits.",
      button: "Préparer mon dossier de commandite",
      back: "← Retour à la fiche",
      eyebrow: (s) => "Dossier de commandite · " + s,
      lead: "Remplissez ce que vous avez, téléchargez votre dossier (.zip) et envoyez-le par courriel à la personne qui vous a donné accès à ce formulaire. Vos résultats viennent des classements : rien à taper.",
      steps: ["Remplissez", "Téléchargez le .zip", "Envoyez-le par courriel"],
      resume: "Reprendre un dossier (.zip)",
      sample: "Voir un exemple de dossier (PDF)",
      stored: "Rien n’est envoyé au site : tout reste dans votre navigateur jusqu’au téléchargement.",
      loading: "Chargement du formulaire…",
      loadError: "Le formulaire n’a pas pu être chargé. Vérifiez votre connexion, puis rechargez la page.",
      codeTitle: "Votre code d’accès",
      codeText: "Entrez le code qu’on vous a donné pour ouvrir votre formulaire de dossier de commandite.",
      codeLabel: "Code",
      codeOpen: "Ouvrir le formulaire",
      codeWrong: "Code incorrect.",
      codeLost: "Code perdu ? Demandez-le à la personne qui vous a donné accès à ce formulaire.",
      settingsTitle: "Réglages du dossier",
      settingsAside: "Pour tout le dossier",
      langsTitle: "Langues du dossier",
      langsSub: "Langues (un PDF par langue)",
      langs: { fr: "Français", en: "Anglais", both: "Les deux" },
      langsNote: "Vous écrivez vous-même chaque texte dans chaque langue choisie : il est imprimé tel que vous l’avez tapé, sans traduction ni correction.",
      legendTitle: "Légende des résultats",
      legend: "Afficher la légende sous les résultats",
      legendHint: "Le bloc « Légende » au bas de la page Résultats (podium, DNF, Joker…). Sans elle, la grille a plus de place.",
      preview: {
        open: "Aperçu du PDF",
        short: "Aperçu",
        title: "Aperçu du dossier",
        close: "Fermer",
        making: "Mise en page…",
        pages: (n) => String(n) + (n > 1 ? " pages" : " page"),
        langs: { fr: "Français", en: "English" },
        zoom: "Taille des pages",
        zoomFit: "Page entière",
        zoomReal: "Taille réelle",
        notice: "Aperçu : il peut différer légèrement du PDF final.",
        more: (n) => "Voir quoi (" + String(n) + ")",
        lead: "Les pages telles que le PDF les imprimera, avec ce que vous avez saisi. Refait à chaque ouverture.",
        results: (date) => "Résultats et chiffres au " + date + " : le PDF reprend ceux du jour où le club le produit (une course de plus change des chiffres).",
        mark: "La marque « APERÇU — NON PAYÉ », la bande rouge, vos coordonnées et le code QR masqués : rien de cela dans la version finale.",
        browser: (name2) => "Votre navigateur (" + name2 + ") n’est pas celui qui fait le PDF (Chrome). Dans nos essais, l’écart est d’au plus 1 mm, sans aucune ligne ni page différente.",
        over: (n) => (n > 1 ? String(n) + " textes dépassent" : "Un texte dépasse") + " sa limite : le PDF ne peut pas être fait ainsi, corrigez-le dans le formulaire.",
        engines: {
          ios: "moteur Safari de l’iPhone ou de l’iPad",
          safari: "Safari",
          firefox: "Firefox",
          other: "ce navigateur"
        }
      },
      inLang: { fr: "Français", en: "Anglais" },
      check: "Vérifier",
      download: "Télécharger mon dossier (.zip)",
      ok: "Tout est prêt à télécharger.",
      empty: (n) => String(n) + (n > 1 ? " champs vides : ils afficheront « à venir »." : " champ vide : il affichera « à venir »."),
      over: (n) => (n > 1 ? String(n) + " textes trop longs : raccourcissez-les" : "1 texte trop long : raccourcissez-le") + " pour télécharger.",
      size: (mb) => "Taille du dossier : " + mb + " Mo",
      tooBig: "Plus de 18 Mo : un courriel risque de le refuser. Choisissez des photos plus légères, ou envoyez le fichier par un service de partage (WeTransfer, Google Drive…).",
      making: "Préparation du dossier…",
      reading: "Lecture du dossier…",
      badZip: "Ce fichier n’est pas un dossier de commandite.",
      otherRider: (name2) => "Ce dossier est celui de « " + name2 + " », pas de ce pilote.",
      onlyImages: "JPEG, PNG ou WebP seulement.",
      duplicate: (place2) => "Cette image est déjà la photo « " + place2 + " ». Choisissez une autre image : les 6 photos doivent être différentes.",
      error: (m) => "Erreur : " + m,
      doneTitle: "Dernière étape : envoyez votre dossier",
      doneText: (f) => "Votre dossier est téléchargé : « " + f + " ». Joignez ce fichier à un courriel à la personne qui vous a donné accès à ce formulaire.",
      doneKeep: "Gardez le fichier : « Reprendre un dossier (.zip) » le rouvre pour le modifier.",
      doneIphone: "Sur iPhone : le fichier est dans l’app Fichiers › Téléchargements ; dans Mail, touchez « Joindre un fichier ».",
      doneAgain: "Modifier encore",
      clear: "Tout effacer",
      clearTitle: "Tout effacer ?",
      clearText: (w) => "Les textes et les photos de ce formulaire seront retirés. Un dossier déjà téléchargé (.zip) n’est pas touché. Pour confirmer, tapez " + w + " ci-dessous.",
      clearWord: "EFFACER",
      clearLabel: "Mot à taper",
      clearYes: "Oui, tout effacer",
      clearNo: "Annuler",
      cleared: "Le formulaire est vide : tout a été effacé.",
      examples: {
        see: "✨ Voir un exemple",
        hide: "Masquer l’exemple",
        fromSeason: (s) => "Exemple tiré de votre saison " + s + " : complétez les [crochets].",
        toComplete: "Exemple à compléter : remplacez chaque [crochet] par vos mots.",
        pick: "Choisissez une phrase, ou écrivez la vôtre.",
        use: "Utiliser cet exemple",
        useOne: "Utiliser",
        replace: "Remplacer votre texte par l’exemple ?",
        yes: "Oui, remplacer",
        no: "Non",
        left: "Complétez les [crochets] de ce texte.",
        blocked: (n) => "Complétez les [crochets] de " + (n > 1 ? String(n) + " champs" : "1 champ") + " pour télécharger."
      },
      ai: {
        title: "Remplir avec une IA",
        aside: "Facultatif",
        lead: "Une IA vous pose des questions à choix, une à la fois : vous répondez par une lettre. Elle écrit ensuite vos textes dans chaque langue du dossier, à la bonne longueur. Vous relisez tout avant de télécharger.",
        step1: "Copiez le texte qui explique à l’IA quoi faire. Vous le collerez ensuite dans l’IA.",
        copy: "Copier le texte pour l’IA",
        copyAgain: "Copier encore",
        nextTitle: "Texte copié ! Maintenant :",
        next: [
          "Ouvrez une IA, celle que vous voulez :",
          "Cliquez dans la case où l’on écrit un message, puis collez : clic droit › Coller (ou Ctrl+V ; sur Mac ⌘V ; sur téléphone, appui long › Coller).",
          "Envoyez le message : l’IA commence à vous poser ses questions."
        ],
        seeText: "Voir le texte pour l’IA",
        hideText: "Masquer le texte pour l’IA",
        copyFailed: "Le texte n’a pas pu être copié : ouvrez « Voir le texte pour l’IA », sélectionnez-le en entier et copiez-le.",
        step2: "Répondez à ses questions par une lettre (A, B, C…). « Passer » laisse une réponse vide.",
        step3: "À la fin, elle vous donne un bloc de texte avec un bouton « Copier ». Cliquez dessus, puis collez ici :",
        replyLabel: "Réponse de l’IA",
        replyHolder: "{ … }",
        fill: "Remplir le formulaire",
        privacy: "Ce que vous écrivez à l’IA lui est envoyé, selon ses propres conditions ; ce site, lui, n’envoie rien. N’y mettez ni adresse, ni courriel, ni téléphone.",
        empty: "Collez d’abord la réponse de l’IA.",
        bad: "Cette réponse n’est pas lisible. Copiez tout le bloc de code que l’IA vous a donné : il commence par { et finit par }.",
        otherRider: (name2) => "Cette réponse est pour « " + name2 + " », pas pour ce pilote.",
        nothing: "Cette réponse ne contient aucun texte à mettre dans le formulaire.",
        replaceQ: (n) => n > 1 ? "La réponse remplace " + String(n) + " champs que vous avez déjà remplis :" : "La réponse remplace 1 champ que vous avez déjà rempli :",
        yes: "Oui, remplacer",
        keep: "Non, garder mes textes",
        cancel: "Annuler",
        done: (n) => (n > 1 ? String(n) + " champs remplis." : n === 1 ? "1 champ rempli." : "Aucun champ changé.") + " Relisez chaque texte : il sera imprimé tel quel.",
        kept: (other) => "Les textes en " + other + " sont gardés aussi : choisissez « Les deux » plus haut pour les voir.",
        over: (n) => n > 1 ? String(n) + " textes trop longs, en rouge : raccourcissez-les, ou demandez à l’IA de les raccourcir." : "1 texte trop long, en rouge : raccourcissez-le, ou demandez à l’IA de le raccourcir.",
        langName: { fr: "français", en: "anglais" },
        prompt: {
          intro: "Tu vas m’aider à remplir mon dossier de commandite de pilote de supermoto (un formulaire du site Canadian Supermoto). Ce dossier sert à convaincre des entreprises de me commanditer : un ton positif, concret, sans exagérer.",
          knownH: "Ce que le site sait déjà de moi (c’est vrai, ne le change pas)",
          rider: "Pilote : ",
          season: (f) => "Saison " + f.season + " : " + (f.starts ? plural2(f.starts, "départ", "départs") + (f.wins ? ", " + plural2(f.wins, "victoire", "victoires") : "") + (f.podiums ? ", " + plural2(f.podiums, "podium", "podiums") : "") + (f.titles ? ", " + plural2(f.titles, "titre", "titres") : "") : "aucune course encore") + (f.classes.length ? " ; catégories : " + f.classes.join(", ") : "") + (f.champs.length ? " ; championnats : " + f.champs.join(", ") : "") + (f.best ? " ; meilleur classement : " + (f.best.rank === 1 ? "1er" : String(f.best.rank) + "e") + " en " + f.best.cls + " (" + f.best.code + ")" : "") + ".",
          bikes: (codes) => "Une moto par discipline : " + codes.join(", ") + ".",
          talkH: "Comment me parler",
          talk: [
            "Je ne suis pas informaticien : des phrases courtes, des mots simples, et vouvoie-moi.",
            "N’emploie jamais les mots « JSON », « clé », « champ », « modèle » ni « caractères », ni les noms techniques (bio, hometown…) : utilise les noms du formulaire, ceux entre « » dans la liste plus bas.",
            "Ne me parle pas des limites de longueur : respecte-les toi-même.",
            "N’invente jamais un résultat, un nom, un chiffre ni un fait sur moi : ce que tu ne sais pas, tu me le demandes.",
            "Ne me demande jamais mon adresse, mon courriel ni mon téléphone."
          ],
          firstH: "Comment me poser les questions",
          first: "Pose-moi des questions à choix, une seule à la fois, et attends ma réponse avant la suivante. Ton premier message commence tout de suite, sans rien d’autre avant, par : « Bonjour ! Je vais vous poser N questions courtes. Répondez par une lettre. », puis la question 1. Chaque question exactement dans cette forme (deux exemples) :",
          shape: [
            "**Question 5 sur 14 · Votre courte bio**",
            "Laquelle vous ressemble le plus ?",
            "A. « … »",
            "B. « … »",
            "C. « … »",
            "D. Aucune : j’écris la mienne",
            "P. Passer",
            "Répondez par une lettre (ex. : B).",
            "",
            "**Question 9 sur 14 · Votre budget : les pneus**",
            "Environ combien dépensez-vous en pneus par saison ?",
            "A. 1 000 $",
            "B. 2 000 $",
            "C. 3 000 $",
            "D. Autre montant : je l’écris",
            "P. Passer",
            "Répondez par une lettre (ex. : B)."
          ],
          rules: [
            "Toujours 2 à 4 choix A, B, C…, puis un choix « Autre » (« je l’écris ») et « P. Passer ». Seule exception : ce qui ne se devine pas (voir plus bas).",
            "Les textes que tu peux écrire à partir de ma saison (la bio, la citation, les objectifs) : 3 propositions différentes, chacune en entier, comme choix A, B, C.",
            "Ce que tu ne peux pas savoir : des choix plausibles quand c’est possible (l’année de début : les 3 dernières années ; l’équipe : « Aucune, je roule seul(e) » ; les réseaux : Instagram, Facebook, TikTok, YouTube, avec « plusieurs lettres possibles (ex. : A C) » ; les abonnés et les montants : des valeurs rondes typiques). Pour le reste (ville, âge, nom du compte, partenaires), une question simple : « Écrivez-la simplement (ex. : Sherbrooke), ou P pour passer. »",
            "Le budget et les forfaits : une question par dépense ou par forfait déjà dans le modèle, pour son montant ou son prix.",
            "Si je choisis « Autre », demande-moi seulement : « Écrivez votre réponse : ». Si j’écris autre chose qu’une lettre, comprends-le et continue.",
            "Si le modèle contient déjà des réponses, ta toute première question est : « Vous avez déjà rempli une partie du formulaire. A. Compléter seulement ce qui manque · B. Revoir mes réponses une à une ». Avec A, ne pose aucune question sur ce qui est déjà rempli. Avec B, pour chaque réponse déjà donnée, une question : « A. Garder la mienne : « … » », puis 2 ou 3 meilleures versions (B, C, D), « Autre » et « P. Passer ». Si le modèle est vide, commence directement.",
            "Un texte déjà rempli dans une seule langue : écris toi-même l’autre langue, avec le même sens, sans me poser de question.",
            "Les textes du dossier (« {pitch} », « {activation} », « {thanks} », « {noPartners} ») : 3 propositions chacun, tirées de ma saison, comme les autres textes ; « P. Passer » laisse le dossier utiliser son texte habituel.",
            "« {feats} » : demande-moi si j’ai un prix, une course hors du classement du club ou une première à mentionner (4 au plus) ; pour chacun, l’année, un titre et une phrase. Ne remplis jamais « {source} » toi-même : demande-moi un lien seulement si j’en ai un.",
            "« {past} » : mes résultats finaux des saisons d’avant (l’année, la catégorie, le rang, le championnat), 10 au plus. Demande-les : n’invente ni un résultat ni un rang.",
            "« {involvement} » : ce que je fais pour le sport hors de la piste (camp de jour, formation, bénévolat), 3 lignes au plus. « {spoken} » : les langues que je parle et mon niveau, choisis dans les listes données plus bas.",
            "Les questions dans cet ordre : vous (ville, âge, année de début, équipe, langues parlées), vos motos, vos textes (bio, citation, objectifs), au-delà du classement (faits marquants, saisons précédentes, implication), vos réseaux sociaux, votre budget, vos forfaits, vos partenaires, puis les textes du dossier. 20 questions au plus : regroupe ce qui va ensemble."
          ],
          langs: (page, other) => "Écris toujours chaque texte en français et en anglais, même si je n’ai choisi qu’une langue pour mon dossier : je pourrai changer d’avis. Dans tes questions, montre-moi seulement le " + page + " ; l’" + other + ", tu l’écris toi-même à la fin, avec le même sens, dit naturellement (pas traduit mot à mot).",
          afterH: "Après la dernière question",
          after: [
            "Montre un court résumé de mes réponses, numéroté, puis demande : « Tout est bon ? A. Oui, donnez-moi le résultat · B. Je veux changer une réponse (écrivez son numéro) ».",
            "À « A », réponds par cette phrase : « C’est prêt ! Cliquez sur « Copier » en haut du bloc ci-dessous, collez-le dans le formulaire (étape 3), puis cliquez sur « Remplir le formulaire ». », suivie d’un seul bloc de code json, dans la forme du modèle plus bas. Rien après le bloc. Une question passée reste vide.",
            "Si je veux changer quelque chose ensuite, redonne le bloc complet, de la même façon."
          ],
          techH: "Pour toi seulement (ne me le montre jamais)",
          tech: [
            'Le bloc : exactement les clés du modèle, rien d’autre, sans commentaire. Un texte vide : "". Un nombre inconnu : null.',
            "Montants et prix en dollars canadiens : un nombre entier, sans symbole (1500). Âge, année et abonnés : un nombre entier.",
            "Un texte déjà rempli dans le modèle : recopie-le tel quel, sauf si je te demande de le changer.",
            "spoken[].lang : un de fr (français), en (anglais), es (espagnol), it (italien), pt (portugais), de (allemand), ar (arabe), zh (mandarin). spoken[].level : un de native (langue maternelle), fluent (courant), intermediate (intermédiaire), basic (notions). Une autre langue n’est pas possible : ne la mets pas.",
            "Respecte chaque longueur ci-dessous, espaces compris : compte-les. Le formulaire refuse un texte trop long ; plus court, c’est mieux."
          ],
          mapH: "Chaque clé, son nom dans le formulaire et sa longueur :",
          entry: (key, label2, length) => "- " + key + " → « " + label2 + " » : " + length,
          chars: (n) => String(n) + " caractères au plus",
          lines: (n, chars) => String(n) + " lignes au plus, " + String(chars) + " caractères par ligne",
          rows: (n) => String(n) + " au plus",
          number: "un nombre entier",
          model: "Le modèle :"
        }
      },
      form: {
        sections: {
          photos: "Photos",
          identity: "Identité",
          bikes: "Motos",
          words: "En ses mots",
          beyond: "Au-delà du classement",
          social: "Réseaux sociaux",
          budget: "Budget de la saison",
          packages: "Forfaits de commandite",
          partners: "Partenaires actuels",
          contact: "Contact",
          texts: "Textes du dossier"
        },
        fields: {
          cover: "1 · couverture",
          portrait: "2 · portrait",
          profileLeft: "3 · profil-gauche",
          profileCenter: "4 · profil-centre",
          profileRight: "5 · profil-droite",
          offer: "6 · forfaits",
          credit: "Crédit photo",
          displayName: "Nom affiché",
          age: "Âge",
          hometown: "Ville ou région",
          since: "En course depuis (année)",
          team: "Équipe",
          bio: "Courte bio",
          quote: "Citation",
          goals: "Objectifs de la prochaine saison",
          feats: "Faits marquants",
          source: "Source",
          past: "Saisons précédentes",
          involvement: "Implication",
          spoken: "Langues parlées",
          network: "Réseau",
          handle: "Compte",
          url: "Lien",
          followers: "Abonnés",
          reach: "Portée moyenne d’une publication",
          label: "Dépense",
          amount: "Montant ($)",
          name: "Nom",
          price: "Prix ($)",
          benefits: "Contreparties",
          logo: "Logo",
          email: "Courriel",
          phone: "Téléphone",
          pitch: "Introduction de « La tournée »",
          activation: "Idées d’activation",
          thanks: "Mot de la fin (bloc « Merci »)",
          noPartners: "Phrase quand il n’y a aucun partenaire"
        },
        hints: {
          cover: "Page 1 du dossier, en haut. En action, horizontale, 3000 px de large ou plus, le fichier original.",
          portrait: "Page Profil (p. 2), grande à gauche. Verticale, en combinaison.",
          profileLeft: "Page Profil (p. 2), bandeau du bas, à gauche. Horizontale.",
          profileCenter: "Page Profil (p. 2), bandeau du bas, au centre. Horizontale.",
          profileRight: "Page Profil (p. 2), bandeau du bas, à droite. Horizontale.",
          offer: "Page Forfaits (p. 6), grand bandeau du bas, toute la largeur. Horizontale, très large.",
          displayName: "Comme il sera imprimé, avec les accents et les majuscules : « Chelsea Parent-Boursier ».",
          hometown: "La ville ou la région d’où vient le pilote. Ex. « Trois-Rivières, QC ».",
          bio: "3 ou 4 phrases : le parcours, la motivation, ce qui distingue le pilote.",
          quote: "Une phrase forte, dans les mots du pilote. Affichée en grand.",
          goals: "3 au plus, un par ligne. Ex. « Viser un podium au Championnat canadien ».",
          packages: "Une proposition à adapter, facultative : changez les noms et les contreparties, inscrivez vos prix, ajoutez ou retirez des forfaits.",
          budget: "Dépenses proposées : inscrivez vos montants et retirez ce qui ne s’applique pas. Une dépense sans montant n’apparaît pas.",
          reach: "Combien de personnes voient une de vos publications, en moyenne. Instagram : Tableau de bord professionnel › Statistiques › Comptes atteints sur 30 jours, divisé par le nombre de publications. Facebook : Meta Business Suite › Statistiques. Ex. « 1 200 personnes ».",
          texts: "Laissé vide, chaque champ prend le texte par défaut, affiché en gris.",
          credit: "Le nom du photographe, imprimé discrètement sur la couverture. Facultatif.",
          age: "Facultatif. Affiché à côté du nom.",
          since: "L’année de la première course. Ex. 2016.",
          team: "Le nom de l’équipe ou de l’écurie, s’il y en a une.",
          bike: "Marque, modèle et cylindrée. Ex. « KTM 450 SMR ».",
          network: "Ex. Instagram, Facebook, TikTok, YouTube.",
          handle: "Le nom du compte. Ex. @nom.pilote",
          url: "Facultatif. Le lien vers la page.",
          followers: "Le nombre d’abonnés, sans espace. Ex. 1250",
          name: "Le nom affiché du forfait. Ex. Platine.",
          price: "Le prix en dollars, sans le signe. Laissez vide pour ne pas l’afficher.",
          benefits: "Ce que le commanditaire reçoit, une contrepartie par ligne, 4 au plus.",
          label: "Le nom de la dépense. Ex. Pneus.",
          amount: "Le montant en dollars pour la saison, sans le signe.",
          partnerName: "Le nom du partenaire actuel.",
          logo: "PNG transparent ou SVG de préférence.",
          contactName: "La personne que le commanditaire doit joindre.",
          email: "L’adresse qui recevra les réponses des commanditaires.",
          phone: "Facultatif. Ex. 819 555-0100",
          pitch: "Remplace l’introduction de la page « La tournée ».",
          activation: "Ce que le pilote propose de faire pour ses commanditaires, un par ligne, 4 au plus.",
          thanks: "Le mot de la fin, sous « Merci », à la dernière page.",
          noPartners: "Affichée seulement si aucun partenaire n’est inscrit."
        },
        intros: {
          photos: "Six photos, une par emplacement du dossier. Choisissez des originaux nets ; une même image ne peut servir qu’une fois.",
          identity: "Ce qui présente le pilote sur la couverture et la page Profil.",
          bikes: "Une ligne par discipline courue cette saison, tirée des classements.",
          words: "Le pilote en ses mots : ce qui donne envie à un commanditaire de le suivre.",
          beyond: "Ce que les résultats officiels ne montrent pas. Le dossier l’imprime sur sa propre page, avec la mention « Informations fournies par l’athlète ».",
          social: "L’audience en ligne, un argument fort pour un commanditaire.",
          packages: "Des propositions à adapter : changez-les, ajoutez-en ou retirez-les.",
          budget: "Les dépenses de la saison ; une dépense sans montant n’apparaît pas.",
          partners: "Les commanditaires actuels, affichés à la dernière page.",
          texts: "Des textes par défaut sont déjà prévus (en gris).",
          contact: "La personne qui répond aux commanditaires."
        },
        rowNames: {
          social: "Réseau",
          packages: "Forfait",
          "budget.items": "Dépense",
          partners: "Partenaire",
          bikes: "",
          feats: "Fait marquant",
          past: "Résultat",
          spoken: "Langue"
        },
        add: "+ Ajouter",
        remove: "Retirer",
        upload: "Choisir un fichier",
        replace: "Remplacer",
        drop: {
          photo: "Enlever la photo",
          logo: "Enlever le logo",
          askPhoto: "Enlever cette photo ?",
          askLogo: "Enlever ce logo ?",
          yes: "Oui, enlever",
          no: "Non"
        },
        beyond: {
          declared: "Ces informations viennent de vous : le site ne peut pas les vérifier. Le dossier les sépare des résultats officiels, sous la mention « Informations fournies par l’athlète ».",
          feats: "Faits marquants",
          featsHint: "Un prix, une course hors du classement du club, une première… Jusqu’à 4, le plus récent d’abord dans le dossier.",
          addFeat: "+ Ajouter un fait marquant",
          old: "Venu de votre ancien champ « Saisons précédentes » : vérifiez l’année et le titre, et ajoutez une phrase si vous voulez.",
          year: "Année",
          source: "Source (facultatif)",
          sourceHint: "Un lien vers les résultats, pour qu’un commanditaire puisse vérifier.",
          title: "Titre",
          titleHint: "60 caractères au plus.",
          detail: "En une phrase (facultatif)",
          detailHint: "160 caractères au plus. Sans phrase, le dossier imprime le titre seul.",
          past: "Saisons précédentes",
          pastHint: "Vos résultats finaux des saisons d’avant (le site n’a que la saison en cours) : un résultat par ligne, jusqu’à 10.",
          addResult: "+ Ajouter un résultat",
          cls: "Catégorie",
          rank: "Rang",
          champ: "Championnat",
          involvement: "Implication",
          involvementHint: "Ce que vous faites pour le sport hors de la piste : camp de jour, formation, bénévolat. Jusqu’à 3 lignes.",
          spoken: "Langues parlées",
          spokenHint: "Le dossier les écrit lui-même dans chaque langue : « Anglais (courant) », « English (fluent) ».",
          lang: "Langue",
          level: "Niveau",
          pickLang: "Choisir la langue…",
          pickLevel: "Choisir le niveau…",
          addLang: "+ Ajouter une langue"
        },
        spoken: SPOKEN_FR,
        framing: {
          lead: "Chaque photo s’affiche dans le cadre exact du PDF : ce que vous voyez ici est ce qui sera imprimé. Glissez la photo dans son cadre pour choisir ce qui reste visible (au clavier : les flèches).",
          page: (n) => "Page " + String(n),
          centered: "Cadrage au centre",
          moved: "Cadrage ajusté",
          center: "Recentrer",
          noMove: "Cette photo a déjà la forme du cadre : rien à ajuster.",
          label: (place2) => "Cadrage de la photo « " + place2 + " » : glissez la photo ou utilisez les flèches"
        }
      },
      kit: {
        dec: (x) => x.toFixed(1).replace(".", ","),
        visibilityLead: (finals, weekends) => "Avec plusieurs catégories au programme, la moto revient en piste finale après finale : en moyenne " + finals + " finales par week-end, sur " + String(weekends) + " week-ends de course. Chaque départ est une nouvelle occasion de montrer votre marque.",
        activationIdeas: [
          "Une publication de remerciement après chaque week-end de course",
          "Photos et vidéos de la moto à vos couleurs, libres d’utilisation pour votre marque",
          "Une visite ou une journée de présentation à votre commerce",
          "Un rapport de fin de saison : résultats, photos et retombées"
        ],
        thanksText: "Merci de votre intérêt. Parlons de la façon dont votre marque peut prendre le départ avec nous.",
        noPartners: "Votre marque peut être la première à soutenir cette saison.",
        proposedPackages: [
          {
            name: "Platine",
            benefits: [
              "Logo principal sur la moto",
              "Logo sur la combinaison",
              "Publication après chaque week-end de course",
              "Rapport de fin de saison"
            ]
          },
          {
            name: "Or",
            benefits: ["Logo sur la moto", "Mention dans les publications de course", "Rapport de fin de saison"]
          },
          {
            name: "Argent",
            benefits: ["Logo sur la remorque ou la tente", "Remerciement sur les réseaux sociaux"]
          }
        ],
        proposedBudget: ["Inscriptions", "Pneus", "Déplacements et hébergement", "Pièces et entretien", "Équipement"],
        example: {
          bioStart: (name2) => name2 + " roule en supermoto depuis [année de début].",
          bioSeason: (f, full) => {
            if (!f.classes.length) return "";
            const classes = full || f.classes.length <= 3 ? andList2(f.classes) : String(f.classes.length) + " catégories";
            if (!f.starts) return " En " + f.season + " : au départ en " + classes + ".";
            const champs = f.champs.length === 1 ? " au " + f.champs[0] : " dans " + String(f.champs.length) + " championnats" + (full || f.champs.length <= 3 ? " (" + f.champs.join(", ") + ")" : "");
            const won = [
              f.titles ? plural2(f.titles, "titre", "titres") : "",
              f.wins ? plural2(f.wins, "victoire", "victoires") : "",
              f.podiums ? plural2(f.podiums, "podium", "podiums") : ""
            ].filter(Boolean);
            return " En " + f.season + " : " + plural2(f.starts, "départ", "départs") + " en " + classes + champs + (won.length ? ", pour " + andList2(won) : "") + ".";
          },
          bioEnd: ["[Ce qui vous a amené à la course, en une phrase.]", "[Ce qui vous distingue sur la piste.]"],
          quotes: [
            "Chaque départ est une nouvelle chance de faire mieux.",
            "Je roule pour progresser, course après course.",
            "Sur la piste, je donne tout jusqu’au dernier tour."
          ],
          goals: (f, next) => [
            firstGoal2(f, next),
            "Courir toute la saison " + next,
            "[Un objectif personnel : meilleurs départs, passer au niveau supérieur…]"
          ],
          reach: "[nombre] personnes par publication"
        }
      }
    },
    check: {
      none: "Aucun problème.",
      errors: (n) => plural2(n, "erreur", "erreurs"),
      warnings: (n) => plural2(n, "avertissement", "avertissements"),
      errorsHelp: "À corriger dans le classeur : en attendant, ce qu'elles touchent n'est pas affiché sur le site.",
      warningsHelp: "À vérifier : quelque chose semble faux. Le classement est affiché.",
      general: "Tout l'onglet",
      blockedTab: "Ce championnat n'est pas affiché tant que ces erreurs ne sont pas corrigées.",
      blockedWorkbook: "Rien n'est affiché sur le site tant que ces erreurs ne sont pas corrigées.",
      workbook: "CONFIG et CALENDAR",
      gala: "Onglet « GALA » (Palmarès et Soirée des champions)",
      highlights: "Onglet « HIGHLIGHTS » (faits saillants)",
      sponsors: "Onglet « SPONSORS » (partenaires)",
      distances: "Onglet « DISTANCES » (carte de la présentation)",
      promotions: "Onglet « PROMOTIONS » (les pilotes qui montent de niveau)",
      mediaKit: "Onglet « MEDIA KIT » (les pilotes à qui le dossier de commandite est offert)",
      season: (season) => "Saison " + season,
      galaPart: {
        title: (season) => "Gala " + season,
        aside: "Ce que la présentation montre, à l'instant",
        help: (slides) => "Le contenu exact du Palmarès et de la présentation au moment où cette page est ouverte : " + plural2(slides, "diapo", "diapos") + ". Le soir du gala, relisez-le juste après avoir saisi la dernière course : seuls les noms et les chiffres peuvent changer, jamais la liste.",
        podiums: "Podiums, dans l'ordre de la présentation",
        champ: "Championnat",
        classes: "Catégories et champions",
        provisional: (done, total) => "Provisoire · " + String(done) + " / " + String(total) + " courses",
        updating: "en mise à jour",
        facts: "Faits saillants, dans l'ordre de l'onglet HIGHLIGHTS",
        head: { n: "#", fact: "Fait", state: "État", now: "Contenu à l'instant" },
        shown: "Affiché",
        pick: "♥ Coup de cœur",
        challenge: "Défi",
        hidden: "Masqué",
        hiddenWhy: "SHOW = NO",
        off: "Aucun fait saillant : l'onglet HIGHLIGHTS a des erreurs pour cette saison (voir l'onglet Classeur).",
        sponsors: "Partenaires, dans l'ordre de la présentation",
        sponsorsHead: { tier: "Niveau", partners: "Partenaires" },
        noTier: "—",
        noLogo: "sans logo : son nom",
        sponsorsNone: "Aucun partenaire : la présentation ne remercie aucun partenaire (voir l'onglet Classeur).",
        promotions: "Promotions, dans l'ordre de l'onglet PROMOTIONS",
        promotionsHead: { discipline: "Discipline", rider: "Pilote", plates: "Plaques", passage: "Promotion" },
        promotionsOff: "Aucune promotion : l'onglet PROMOTIONS a des erreurs pour cette saison (voir l'onglet Classeur)."
      },
      tab: (tab, code) => "Onglet « " + tab + " » (" + code + ")",
      tabNotShown: "Ce championnat n'est pas affiché : voir les erreurs de CONFIG.",
      between: "Entre les onglets",
      noSeason: "Aucune saison dans CONFIG."
    },
    // The dates, the places.
    dateRange: dateRange2,
    raceDays: raceDays2,
    /** "1<small>er</small>", "7<small>e</small>". */
    ord: (n) => n === 1 ? "1<small>er</small>" : String(n) + "<small>e</small>",
    /** "1er", "7e". */
    ordText: (n) => String(n) + (n === 1 ? "er" : "e"),
    // The problems of the workbook.
    issuePlace: issuePlace2,
    issues: issues2,
    cards: cards2,
    jokerHelp: jokerHelp2,
    validate: { error: "ERREUR", warning: "avertissement" },
    server: {
      lookMissing: "L’extension CSM UI (version %s ou plus récente) doit être installée et activée.",
      noWorkbook: "Aucun ID de classeur Google n’a été donné (attribut classeur du shortcode).",
      noAnswer: "Google Sheets n’a pas répondu à temps ou n’a pas pu être joint (%s). Réessayez dans un instant.",
      googleBusy: "Google Sheets ne répond pas pour l’instant (HTTP %d). Réessayez dans une minute.",
      notShared: "Impossible de lire le classeur Google (HTTP %d). Vérifiez qu’il est partagé en « Tous les utilisateurs disposant du lien peuvent consulter ».",
      unreadable: "Le fichier du classeur est illisible. Vérifiez qu’il est partagé en « Tous les utilisateurs disposant du lien peuvent consulter ».",
      badEncoding: "Le classeur contient du texte illisible (encodage des caractères) : il ne peut pas être affiché.",
      unavailable: "Le classement est temporairement indisponible.",
      editorsOnly: "Seuls les éditeurs connectés voient ces détails.",
      checkTitle: "Vérification du classeur",
      adminPrefix: "CSM Standings :"
    }
  };

  // plugins/csm-standings/src/site/i18n.ts
  var LANGUAGES = { en, fr };
  var DEFAULT_LANG = "en";
  function langOf(tag) {
    const code = tag.slice(0, 2).toLowerCase();
    return Object.keys(LANGUAGES).includes(code) ? code : DEFAULT_LANG;
  }
  var T = LANGUAGES[DEFAULT_LANG];
  var current = DEFAULT_LANG;
  function useLanguage(lang) {
    T = LANGUAGES[lang];
    current = lang;
  }
  function siteLanguages() {
    return { languages: Object.keys(LANGUAGES), lang: current };
  }

  // plugins/csm-standings/src/site/check-cards.ts
  var REF_OPEN = "";
  var REF_CLOSE = "";
  var ref = (tab, col, row2) => REF_OPEN + T.cards.ref(tab, col, row2) + REF_CLOSE;
  var short = (col, row2) => colLetter(col) + String(row2);
  var WORKBOOK_TABS = [
    "SEASONS",
    "VENUES",
    "CONFIG",
    "CALENDAR",
    "GALA",
    "HIGHLIGHTS",
    SPONSORS_TAB,
    DISTANCES_TAB,
    PROMOTIONS_TAB,
    MEDIA_KIT_TAB
  ];
  function rowsOf(payload, tab) {
    const workbook = {
      SEASONS: payload.seasons,
      VENUES: payload.venues,
      CONFIG: payload.config,
      CALENDAR: payload.calendar,
      GALA: payload.gala,
      HIGHLIGHTS: payload.highlights,
      SPONSORS: payload.sponsors,
      DISTANCES: payload.distances,
      PROMOTIONS: payload.promotions,
      [MEDIA_KIT_TAB]: payload.mediaKit
    };
    return (WORKBOOK_TABS.includes(tab) ? workbook[tab] : payload.tabs[tab]) ?? [];
  }
  var cellText = (payload, tab, row2, col) => rowsOf(payload, tab)[row2 - 1][col] ?? "";
  var colOf = (payload, tab, header2) => (rowsOf(payload, tab)[0] ?? []).indexOf(header2);
  var field = (payload, tab, row2, header2) => cellText(payload, tab, row2, colOf(payload, tab, header2));
  var codeOf = (tab, place2) => place2.codes[place2.tabs.indexOf(tab)];
  function cardOf(group, place2) {
    switch (group.kind) {
      case "column":
        return columnCard(group, place2);
      case "pending":
        return pendingCard(group, place2);
      case "names":
        return namesCard(group, place2);
      default:
        return isShared(group.issue) ? sharedCard(group.issue, place2) : oneCard(group.issue, place2);
    }
  }
  var inOrder = (codes, place2) => [...new Set(codes)].sort((a, b) => place2.codes.indexOf(a) - place2.codes.indexOf(b));
  function sharedTable(issues3, place2, shown2 = Infinity, withEvent = false) {
    const codes = inOrder(issues3.flatMap(codesOf), place2);
    const rows3 = issues3.map((issue) => {
      const odd = oddOnes(issue);
      const sub = [
        ...issue.values.filter((v) => v.plate !== "").slice(0, 1).map((v) => T.cards.plate(v.plate)),
        ...withEvent ? [issue.event] : []
      ];
      const cell = (code) => {
        const v = valueIn(issue, code);
        return v ? { text: v.found, ref: short(v.col, v.row), odd: odd.has(code) } : { text: T.cards.noRow, odd: odd.has(code) };
      };
      return sub.length ? { label: issue.rider, sub: sub.join(" · "), cells: codes.map(cell) } : { label: issue.rider, cells: codes.map(cell) };
    });
    const head = [T.cards.head.rider, ...codes];
    return rows3.length > shown2 ? { head, rows: rows3.slice(0, shown2), more: rows3.slice(shown2) } : { head, rows: rows3 };
  }
  var sharedBlocks = (issues3, category, place2) => T.cards.blocks.classIn(category, inOrder(issues3.flatMap(codesOf), place2));
  var count = (issue, value) => issue.values.filter((v) => v.found === value).length;
  var at = (v) => ref(v.tab, v.col, v.row);
  function sharedCard(issue, place2) {
    const F = T.cards.fix;
    const { rider, category, event } = issue;
    const base = { where: [event, category, rider], table: sharedTable([issue], place2) };
    if (issue.code === "shared-outcome") {
      const values = [...new Set(issue.values.map((v) => v.found).filter((f) => f !== ""))];
      values.sort((a, b) => count(issue, b) - count(issue, a));
      return {
        ...base,
        severity: "error",
        title: T.cards.titles["shared-outcome"],
        what: T.cards.shared.outcome(event),
        blocks: sharedBlocks([issue], category, place2),
        fixes: values.map((value) => ({
          when: T.cards.happened(rider, value),
          action: F.put(value, issue.values.filter((v) => v.found !== value).map(at))
        }))
      };
    }
    if (issue.code === "shared-missing") {
      const results2 = issue.values.filter((v) => isResult2(v.found));
      const codes = issue.missing.map((m) => m.code);
      return {
        ...base,
        severity: "error",
        title: T.cards.titles["shared-missing"],
        what: T.cards.shared.missing(event, codes),
        blocks: sharedBlocks([issue], category, place2),
        fixes: [
          {
            when: F.raced(rider, codes),
            action: F.addRow(
              issue.missing.map((m) => ref(m.tab)),
              category,
              results2[0].plate,
              rider,
              event,
              colLetter(issue.missing[0].col),
              results2[0].found
            )
          },
          { when: F.otherName(rider, codes), action: F.sameName(rider) },
          { when: F.didNotRace(rider), action: F.put("X", results2.map(at)) }
        ]
      };
    }
    const filled = issue.values.find((v) => v.found !== "");
    return {
      ...base,
      severity: "warning",
      title: T.cards.titles["shared-pending"],
      what: T.cards.shared.pending(event),
      blocks: T.cards.blocks.nothing,
      fixes: [{ when: F.enterNow, action: F.put(filled.found, issue.values.filter((v) => v.found === "").map(at)) }]
    };
  }
  var worded = (changes) => changes.sort((a, b) => a.ref.localeCompare(b.ref, "en", { numeric: true })).map((c) => c.text);
  var set = (place2, from, to) => ({
    ref: place2,
    text: T.cards.change(place2, from, to)
  });
  function change(issue, good, code, place2) {
    const now = valueIn(issue, code);
    if (now === null) {
      if (good === null || !isResult2(good.found)) return null;
      const tab = ref(place2.tabs[place2.codes.indexOf(code)]);
      return { ref: tab, text: T.cards.addRowAt(tab, issue.rider, good.found) };
    }
    if (good === null) {
      if (!isResult2(now.found)) return null;
      const row2 = ref(now.tab, void 0, now.row);
      return { ref: row2, text: T.cards.deleteRowAt(row2, issue.rider) };
    }
    return now.found === good.found ? null : set(at(now), now.found, good.found);
  }
  function columnCard(group, place2) {
    const { issues: issues3, cause, sides, event, category } = group;
    const codes = inOrder(sides.flat(), place2);
    const fixes = sides.map((side) => {
      const targets = codes.filter((c) => !side.includes(c));
      const changes = issues3.flatMap(
        (issue) => targets.flatMap((code) => change(issue, valueIn(issue, side[0]), code, place2) ?? [])
      );
      return {
        when: cause ? T.cards.happened(cause.rider, valueIn(cause, side[0])?.found ?? "X") : T.cards.fix.sideRight(side),
        action: T.cards.fix.copyColumn(event, category, side, targets, changes.length),
        changes: worded(changes)
      };
    });
    return {
      severity: "error",
      title: T.cards.shared.columnTitle(event, category, codes, issues3.length),
      where: [event, category],
      what: cause ? T.cards.shared.columnCause(
        cause.rider,
        sides.map((side) => ({ codes: side, value: valueIn(cause, side[0])?.found ?? null }))
      ) : T.cards.shared.columnNoCause(event),
      table: sharedTable(issues3, place2, cause ? 1 : 3),
      blocks: sharedBlocks(issues3, category, place2),
      fixes
    };
  }
  function pendingCard(group, place2) {
    const { issues: issues3, event, category } = group;
    const codes = (blank) => inOrder(
      issues3.flatMap((i) => i.values.filter((v) => v.found === "" === blank).map((v) => v.code)),
      place2
    );
    const changes = worded(
      issues3.flatMap((i) => {
        const good = i.values.find((v) => v.found !== "").found;
        return i.values.filter((v) => v.found === "").map((v) => set(at(v), "", good));
      })
    );
    return {
      severity: "warning",
      title: T.cards.shared.pendingTitle(event, category, codes(true)),
      where: [event, category],
      what: T.cards.shared.pendingColumn(event, codes(false), codes(true), issues3.length),
      table: sharedTable(issues3, place2, 3),
      blocks: T.cards.blocks.nothing,
      fixes: [
        {
          when: T.cards.fix.enterNow,
          action: T.cards.fix.copyColumn(event, category, codes(false), codes(true), changes.length),
          changes
        }
      ]
    };
  }
  function namesCard(group, place2) {
    const { issues: issues3, category } = group;
    const names = [...new Set(issues3.map((i) => i.rider))];
    const events = [...new Set(issues3.map((i) => i.event))];
    const cellsOf = (name2) => place2.tabs.flatMap(
      (tab) => rowsOf(place2.payload, tab).flatMap(
        (r, i) => i > 0 && r[0] === category && riderKey(r[NAME_COL]) === riderKey(name2) ? [ref(tab, NAME_COL, i + 1)] : []
      )
    );
    return {
      severity: "error",
      title: T.cards.shared.namesTitle(names[0], names[1]),
      where: [category, ...events],
      what: T.cards.shared.names(names[0], names[1], events),
      table: sharedTable(issues3, place2, Infinity, events.length > 1),
      blocks: sharedBlocks(issues3, category, place2),
      fixes: [
        ...names.map((good, k) => ({
          when: T.cards.fix.sameRider(good),
          action: T.cards.fix.rename(good, cellsOf(names[1 - k]))
        })),
        { when: T.cards.fix.twoRiders, action: T.cards.fix.addBoth(names, events) }
      ]
    };
  }
  function oneCard(issue, place2) {
    const tab = place2.ctx.kind === "cross" && "tab" in issue ? issue.tab : place2.tab;
    const what = T.issues[issue.code](issue);
    return {
      severity: issue.severity,
      title: T.cards.titles[issue.code],
      where: whereOf(issue, tab, place2),
      what: what.charAt(0).toUpperCase() + what.slice(1),
      table: tableOf(issue, tab, place2.payload),
      blocks: blocksOf(issue, place2),
      fixes: fixesOf(issue, tab, place2)
    };
  }
  function whereOf(issue, tab, place2) {
    const { ctx, payload } = place2;
    const row2 = issue.row !== void 0 && issue.row > 1 ? issue.row : void 0;
    if (ctx.kind === "seasons") return row2 ? [T.cards.galaSeason(field(payload, "SEASONS", row2, "SEASON"))] : [];
    if (ctx.kind === "venues") return row2 ? [field(payload, "VENUES", row2, "CODE")] : [];
    if (ctx.kind === "config")
      return row2 ? [field(payload, "CONFIG", row2, "SEASON") + " · " + field(payload, "CONFIG", row2, "CODE")] : [];
    if (ctx.kind === "calendar")
      return row2 ? [field(payload, "CALENDAR", row2, "SEASON") + " · " + field(payload, "CALENDAR", row2, "EVENT")] : [];
    if (ctx.kind === "gala") return row2 ? [T.cards.galaSeason(field(payload, "GALA", row2, "SEASON"))] : [];
    if (ctx.kind === "highlights") {
      if ("season" in issue) return [T.cards.galaSeason(issue.season)];
      if (!row2) return [];
      const season = field(payload, "HIGHLIGHTS", row2, "SEASON");
      return [season && T.cards.galaSeason(season), field(payload, "HIGHLIGHTS", row2, "CODE")].filter((s) => s !== "");
    }
    if (ctx.kind === "sponsors") {
      if ("season" in issue) return [T.cards.galaSeason(issue.season)];
      if (!row2) return [];
      const season = field(payload, SPONSORS_TAB, row2, "SEASON");
      return [season && T.cards.galaSeason(season), field(payload, SPONSORS_TAB, row2, "NAME")].filter((s) => s !== "");
    }
    if (ctx.kind === "distances") {
      if (issue.code === "distance-none") return [T.cards.galaSeason(issue.season), issue.from + " – " + issue.to];
      return row2 ? [field(payload, DISTANCES_TAB, row2, "FROM") + " – " + field(payload, DISTANCES_TAB, row2, "TO")] : [];
    }
    if (ctx.kind === "mediaKit") {
      if (!row2) return [];
      const season = field(payload, MEDIA_KIT_TAB, row2, "SEASON");
      return [season && T.cards.galaSeason(season), field(payload, MEDIA_KIT_TAB, row2, "NAME")].filter((s) => s !== "");
    }
    if (ctx.kind === "promotions") {
      if (!row2) return [];
      const season = field(payload, PROMOTIONS_TAB, row2, "SEASON");
      return [season && T.cards.galaSeason(season), field(payload, PROMOTIONS_TAB, row2, "NAME")].filter((s) => s !== "");
    }
    if (ctx.kind === "cross") return tab ? [tab] : [];
    return [issue.category, issue.rider, issue.column].filter((s) => s !== void 0 && s !== "");
  }
  function tableOf(issue, tab, payload) {
    const workbookTab = WORKBOOK_TABS.includes(tab);
    const riderAt = (row2) => {
      const r = rowsOf(payload, tab)[row2 - 1];
      return r[0] + " · " + r[NAME_COL];
    };
    const one = (row2, col, label2) => ({
      label: ref(tab, col, row2),
      sub: label2 ?? (workbookTab ? void 0 : riderAt(row2)),
      cells: [{ text: cellText(payload, tab, row2, col), odd: true }]
    });
    const head = [T.cards.head.cell, T.cards.head.value];
    switch (issue.code) {
      case "name-similar":
      case "name-spelling":
        return {
          head: [T.cards.head.name, T.cards.head.where],
          rows: issue.variants.map((v) => ({
            label: v.name,
            sub: v.places.join(", "),
            cells: [{ text: v.cells.map((c) => T.cards.ref(c.tab, c.col, c.row)).join(" · ") }]
          }))
        };
      case "points-missing":
        return classColumn(issue, tab, payload, issue.points);
      case "points-twice":
        return classColumn(issue, tab, payload, []);
      case "na-mixed":
        return { head, rows: issue.cells.map((c) => one(c.row, issue.col)) };
      case "blank-mixed":
        return { head, rows: issue.rows.map((r) => one(r, issue.col)) };
      case "event-partial":
        return entryTable(issue, tab, payload);
      case "top-twice":
        return { head, rows: issue.riders.map((r) => one(r.row, issue.col)) };
      case "rider-twice":
        return { head, rows: [one(issue.firstRow, NAME_COL), one(issue.row, NAME_COL)] };
      case "column-no-calendar":
        return { head, rows: [one(1, issue.headerCol, issue.found)] };
      case "highlight-incomplete": {
        const row2 = issue.row;
        const cols = ["CODE", ...issue.empty].map((h) => colOf(payload, tab, h));
        return {
          head: ["", "CODE", ...issue.empty],
          rows: [
            {
              label: ref(tab, void 0, row2),
              cells: cols.map((col, i) => ({
                text: cellText(payload, tab, row2, col),
                ref: short(col, row2),
                odd: i === 0
              }))
            }
          ]
        };
      }
      default:
        if (issue.row === void 0 || issue.col === void 0) return null;
        return { head, rows: [one(issue.row, issue.col, workbookTab ? issue.column : void 0)] };
    }
  }
  function classColumn(issue, tab, payload, missing) {
    const col = issue.col;
    const points2 = (text) => /^\d+$/.test(text) ? Number(text) : -1;
    const all = rowsOf(payload, tab).map((r, i) => ({ r, row: i + 1 })).filter(({ r, row: row2 }) => row2 > 1 && r[0] === issue.category).sort((a, b) => points2(b.r[col] ?? "") - points2(a.r[col] ?? "")).map(({ r, row: row2 }) => ({
      label: r[NAME_COL],
      sub: T.cards.ref(tab, col, row2),
      cells: [{ text: r[col] ?? "", odd: points2(r[col] ?? "") < 0 }]
    }));
    const floor = missing.length ? POINTS_SCALE.find((p) => p < Math.min(...missing)) ?? 0 : 0;
    const shows = (text) => points2(text) >= floor || text === "DNF" || text === "DNS";
    const shown2 = all.filter((r) => shows(r.cells[0].text));
    return {
      head: [T.cards.head.rider, issue.column],
      rows: shown2,
      more: all.filter((r) => !shown2.includes(r))
    };
  }
  function entryTable(issue, tab, payload) {
    const col = issue.col;
    const rowsIn = (category) => rowsOf(payload, tab).flatMap((r, i) => i > 0 && r[0] === category ? [i + 1] : []);
    const row2 = (category, entered) => {
      const at2 = rowsIn(category);
      const text = entered ? T.cards.entered : T.cards.blank;
      const first = short(col, at2[0]);
      const last = short(col, at2[at2.length - 1]);
      return { label: category, cells: [{ text, ref: first === last ? first : first + "–" + last, odd: !entered }] };
    };
    const rows3 = [...issue.entered.map((k) => row2(k, true)), ...issue.pending.map((k) => row2(k, false))];
    const order = (r) => rowsIn(r.label)[0];
    return { head: [T.cards.head.category, issue.column], rows: rows3.sort((a, b) => order(a) - order(b)) };
  }
  function blocksOf(issue, place2) {
    const B = T.cards.blocks;
    const { ctx, payload } = place2;
    if (issue.code === "event-partial") return B.partial(issue.column, issue.pending);
    if (ctx.kind === "sponsors") return sponsorBlocks(issue, payload);
    if (ctx.kind === "promotions") {
      if (issue.code === "promotion-no-discipline") return B.promotionPlates(issue.found);
      const season = issue.row === void 0 || issue.row < 2 || issue.code === "promotions-season-unknown" || issue.code === "number-format" ? "" : field(payload, PROMOTIONS_TAB, issue.row, "SEASON");
      return B.promotions(season === "" ? [] : [season]);
    }
    if (ctx.kind === "mediaKit") {
      if (issue.code === "media-kit-header") return B.mediaKitAll;
      if (issue.code === "media-kit-twice" || issue.code === "media-kit-code-twice") return B.nothing;
      return B.mediaKit(field(payload, MEDIA_KIT_TAB, issue.row, "NAME"));
    }
    if (ctx.kind === "distances") {
      if (issue.code === "distance-none") return B.distance(issue.from, issue.to);
      if (issue.code === "distances-missing" || issue.code === "distances-header") return B.distancesAll;
      const row3 = issue.row;
      return B.distance(field(payload, DISTANCES_TAB, row3, "FROM"), field(payload, DISTANCES_TAB, row3, "TO"));
    }
    if (issue.severity === "warning") return issue.code.startsWith("race-day") ? B.raceDays : B.nothing;
    if (issue.code === "gala-order-header" || issue.column === "GALA ORDER") return B.showChamps;
    if (issue.code === "gala-classes-header" || issue.column === "GALA CLASS ORDER") return B.showClasses;
    if (issue.code === "venues-missing" || issue.code === "venues-header") return B.places;
    if (ctx.kind === "highlights") {
      if (issue.code === "highlights-missing") return B.highlights(issue.seasons);
      if ("season" in issue) return B.highlights([issue.season]);
      const season = issue.row === void 0 || issue.row < 2 ? "" : field(payload, "HIGHLIGHTS", issue.row, "SEASON");
      return B.highlights(issue.code === "highlights-season-unknown" || season === "" ? [] : [season]);
    }
    if (issue.code === "tab-missing" || issue.code === "header-missing") return B.everything;
    const row2 = issue.row;
    switch (ctx.kind) {
      case "seasons":
        return issue.column === "RULEBOOK" ? B.seasonRules(field(payload, "SEASONS", row2, "SEASON")) : B.row;
      case "venues":
        return issue.column === "COORDINATES" ? B.mapLink(field(payload, "VENUES", row2, "CODE")) : B.places;
      case "config":
        return issue.code !== "rules-missing" && (issue.code === "championship-twice" || issue.code === "season-invalid" || issue.column === "SEASON" || issue.column === "CODE") ? B.row : B.championship(field(payload, "CONFIG", row2, "CODE"), field(payload, "CONFIG", row2, "SEASON"));
      case "calendar":
        return (issue.column === "VENUE CODE" ? B.place : B.race)(
          field(payload, "CALENDAR", row2, "EVENT"),
          field(payload, "CALENDAR", row2, "SEASON")
        );
      case "gala":
        if (issue.code === "gala-public-invalid") return B.galaPublic(field(payload, "GALA", row2, "SEASON"));
        return issue.code === "gala-header" ? B.galaNone : B.row;
      case "tab":
        return issue.category === void 0 ? B.championship(ctx.code, ctx.season) : B.classIn(issue.category, [ctx.code]);
      default: {
        const order = issue;
        return B.championship(codeOf(order.tab, place2), ctx.season);
      }
    }
  }
  function sponsorBlocks(issue, payload) {
    const B = T.cards.blocks;
    if (issue.code === "sponsors-missing") return B.noSponsors(issue.seasons);
    if (issue.code === "sponsors-none") return B.noSponsors([issue.season]);
    if (issue.code === "sponsors-header") return B.sponsorsAll;
    const row2 = issue.row;
    const name2 = field(payload, SPONSORS_TAB, row2, "NAME");
    switch (issue.code) {
      case "logo-unknown":
        return B.logoName(name2);
      case "tier-invalid":
        return B.tierFallback(name2);
      case "since-invalid":
      case "since-after":
        return B.sinceNone(name2);
      case "number-format":
        return B.sponsorRow(name2, field(payload, SPONSORS_TAB, row2, "SEASON"));
      default:
        return B.row;
    }
  }
  function fixesOf(issue, tab, place2) {
    const F = T.cards.fix;
    const { payload, ctx } = place2;
    const at2 = ref(tab, issue.col, issue.row);
    const column = () => ref(tab, issue.col) + " (" + String(issue.column) + ")";
    const only = (action) => [{ when: F.always, action }];
    switch (issue.code) {
      // A championship's tab.
      case "empty-sheet":
        return only(F.writeHeader("CATEGORY, #, NAME", tab));
      case "header-label":
        return only(F.put(issue.expected, [at2]));
      case "no-events":
        return only(F.addColumns(tab));
      case "event-empty":
      case "event-reserved":
        return only(F.nameEvent(at2));
      case "event-twice":
        return [
          { when: F.sameEvent, action: F.mergeColumns(ref(tab, issue.firstCol), at2) },
          { when: F.otherEvent, action: F.nameEvent(at2) }
        ];
      case "spaces":
        return only(F.put(issue.found.trim().replace(/\s+/g, " "), [at2]));
      case "outside-table":
        return only(F.clear(at2));
      case "category-empty":
        return only(F.fill(at2, "CATEGORY"));
      case "name-empty":
        return only(F.fill(at2, "NAME"));
      case "name-case":
        return only(F.put(issue.found.toUpperCase(), [at2]));
      case "value-invalid":
        return valueFixes(issue.found, at2);
      case "na-mixed":
        return [
          {
            when: F.notHeld,
            action: F.put(
              "N/A",
              issue.cells.map((c) => ref(tab, issue.col, c.row))
            )
          },
          { when: F.held, action: F.replaceNa(column()) }
        ];
      case "blank-mixed":
        return only(F.enterResults(issue.rows.map((r) => ref(tab, issue.col, r))));
      case "event-partial":
        return [
          { when: F.enterNow, action: F.enterClasses(issue.pending, column()) },
          { when: F.notHeld, action: F.naForClass(column()) }
        ];
      case "rider-twice":
        return [
          {
            when: F.sameRiderTwice,
            action: F.mergeRows(ref(tab, void 0, issue.firstRow), ref(tab, void 0, issue.row))
          },
          { when: F.twoNamesakes, action: F.tellApart(ref(tab, NAME_COL, issue.row)) }
        ];
      case "cancelled-race-value":
        return only(F.cancelled(issue.top, [at2]));
      case "top-twice":
        return [
          { when: F.notRun, action: F.cancelled(issue.top, [column()]) },
          {
            when: F.run,
            action: F.oneWinner(
              issue.top,
              issue.riders.map((r) => ref(tab, issue.col, r.row))
            )
          }
        ];
      case "points-off-scale":
        return only(F.scale(at2, issue.scale));
      case "points-twice":
        return [
          { when: F.typo, action: F.samePoints(issue.points, column()) },
          { when: F.wanted, action: F.ignore }
        ];
      case "points-missing":
        return issue.points.flatMap((p) => [
          { when: F.placeMissing(p), action: F.givePoints(p, column()) },
          { when: F.wanted, action: F.ignore }
        ]);
      case "zero-while-last-free":
        return [
          { when: F.inThePoints(issue.last), action: F.put(String(issue.last), [at2]) },
          { when: F.wanted, action: F.ignore }
        ];
      case "joker-class-missing":
      case "joker-event-missing":
        return [
          { when: F.renamed, action: F.writeExactly(issue.found, tab) },
          { when: F.jokerChange, action: F.askSite }
        ];
      case "number-format":
        return only(F.formatAuto(at2));
      // CONFIG, CALENDAR, GALA.
      case "tab-missing":
        return only(issue.found === "SEASONS" ? F.createSeasons : F.createTab(issue.found));
      case "venues-missing":
        return only(F.createVenues);
      case "header-missing":
      case "venues-header":
      case "highlights-header":
      case "promotions-header":
      case "media-kit-header":
      case "sponsors-header":
      case "distances-header":
      case "gala-header":
      case "gala-order-header":
      case "gala-classes-header":
        return only(F.writeHeader(issue.expected, tab));
      case "cell-empty":
        return only(issue.column === "COORDINATES" ? F.copyPoint(at2) : F.fill(at2, String(issue.column)));
      case "coordinates-invalid":
        return only(F.copyPoint(at2));
      case "rulebook-unknown":
        return [
          { when: F.editionTypo, action: F.writeEdition(at2) },
          { when: F.newEdition, action: F.askEdition(issue.found) }
        ];
      case "season-unknown":
        return [
          { when: F.seasonTypo, action: F.writeSeason(at2) },
          { when: F.seasonNew, action: F.addSeasonRow(issue.found) }
        ];
      case "venue-unknown":
        return [
          { when: F.venueTypo, action: F.writeVenueCode(at2) },
          { when: F.venueNew, action: F.addVenueRow(issue.found) }
        ];
      case "season-invalid":
        return only(F.writeSeason(at2));
      case "order-invalid":
      case "order-twice":
        return only(F.writeOrder(at2));
      case "championship-twice":
      case "season-twice":
      case "venue-twice":
      case "gala-season-twice":
      case "sponsor-twice":
      case "distance-twice":
      case "promotion-twice":
      case "media-kit-twice":
        return only(F.deleteRow(ref(tab, void 0, issue.row), ref(tab, void 0, issue.firstRow)));
      case "tab-not-found": {
        const close = payload.tabNames.filter(
          (t) => t !== issue.found && t.replace(/\s+/g, "") === issue.found.replace(/\s+/g, "")
        );
        return [
          ...close.map((name2) => ({ when: F.tabTypo(name2), action: F.put(name2, [at2]) })),
          { when: F.tabToCreate, action: F.createTab(issue.found) }
        ];
      }
      case "rules-missing": {
        const season = field(payload, "CONFIG", issue.row, "SEASON");
        const seasonRow = rowsOf(payload, "SEASONS").findIndex(
          (r, i) => i > 0 && r[colOf(payload, "SEASONS", "SEASON")] === season
        );
        return [
          {
            when: F.editionTypo,
            action: F.writeEdition(ref("SEASONS", colOf(payload, "SEASONS", "RULEBOOK"), seasonRow + 1))
          },
          { when: F.newEdition, action: F.askRules(issue.rulebook, issue.found) }
        ];
      }
      case "url-invalid":
        return only(F.writeUrl(at2));
      case "color-invalid":
        return only(F.writeColor(at2));
      case "date-invalid": {
        const m = /^(\d{1,2})[/.-](\d{1,2})[/.-](\d{4})$/.exec(issue.found);
        const date = m ? m[3] + "-" + m[2].padStart(2, "0") + "-" + m[1].padStart(2, "0") : null;
        return date ? [{ when: F.dateGuess(date), action: F.put(date, [at2]) }] : only(F.writeDate(at2));
      }
      case "dates-reversed":
        return only(
          F.swapDates(ref(tab, colOf(payload, tab, "START"), issue.row), ref(tab, colOf(payload, tab, "END"), issue.row))
        );
      case "championship-unknown":
        return [
          { when: F.codeTypo, action: F.writeCode(at2, issue.season) },
          { when: F.codeMissing, action: F.addConfigRow(issue.found, issue.season) }
        ];
      case "event-overlap":
        return [
          { when: F.sameEvent, action: F.deleteRow(ref(tab, void 0, issue.row), ref(tab, void 0, issue.firstRow)) },
          { when: F.otherEvent, action: F.nameEvent(ref(tab, colOf(payload, tab, "EVENT"), issue.row)) }
        ];
      case "race-days-empty":
      case "race-day-invalid":
      case "race-day-outside":
      case "race-days-order":
        return [
          {
            when: F.raceDaysKnown,
            action: F.raceDays(
              at2,
              field(payload, tab, issue.row, "START"),
              field(payload, tab, issue.row, "END")
            )
          },
          { when: F.raceDaysUnknown, action: issue.code === "race-days-empty" ? F.ignore : F.clear(at2) }
        ];
      case "language-unknown":
        return [
          { when: F.languageTypo, action: F.writeLanguage(at2, issue.languages) },
          { when: F.languageUnneeded, action: F.deleteColumn(at2) }
        ];
      case "classes-missing":
        return only(
          F.addClass(issue.found, ref("CONFIG", colOf(payload, "CONFIG", "SITE CLASS ORDER"), issue.configRow))
        );
      case "classes-twice":
        return only(
          F.removeClass(issue.found, ref("CONFIG", colOf(payload, "CONFIG", "SITE CLASS ORDER"), issue.configRow))
        );
      case "gala-classes-missing":
        return only(F.addClass(issue.found, at2));
      case "gala-classes-twice":
        return only(F.removeClass(issue.found, at2));
      case "gala-season-unknown":
      case "highlights-season-unknown":
      case "sponsors-season-unknown":
      case "promotions-season-unknown":
      case "media-kit-season-unknown":
        return [
          { when: F.seasonTypo, action: F.writeSeason(at2) },
          { when: F.rowUnneeded, action: F.deleteRow(ref(tab, void 0, issue.row)) }
        ];
      case "gala-public-invalid":
        return only(F.writePublic(at2));
      case "gala-code-empty":
        return only(F.writeGalaCode(at2));
      case "media-kit-code-empty":
        return only(F.writeKitCode(at2));
      case "media-kit-code-twice":
        return only(F.changeKitCode(at2, ref(tab, issue.col, issue.firstRow)));
      // HIGHLIGHTS.
      case "highlights-missing":
        return only(F.createHighlights);
      case "highlights-none":
        return only(F.addHighlights(issue.season));
      case "highlight-missing":
        return [
          { when: F.wantShown, action: F.addHighlight(issue.season, issue.found, true) },
          { when: F.wantHidden, action: F.addHighlight(issue.season, issue.found, false) }
        ];
      case "highlight-twice":
        return [
          {
            when: F.sameHighlight,
            action: F.deleteRow(ref(tab, void 0, issue.row), ref(tab, void 0, issue.firstRow))
          },
          { when: F.otherHighlight, action: F.renameCode(at2) }
        ];
      case "show-invalid":
        return only(F.writeShow(at2));
      case "trophy-invalid":
        return [
          { when: F.wantTrophy, action: F.put("YES", [at2]) },
          { when: F.noTrophy, action: F.clear(at2) }
        ];
      case "highlight-computed-content":
        return only(F.clear(at2));
      case "highlight-value-invalid": {
        const m = /^(\d[\d\s]*?)\s*[^\d.,]*$/.exec(issue.found);
        const value = m ? m[1].replace(/\s/g, "") : "";
        return value ? [{ when: F.valueIs(value), action: F.put(value, [at2]) }] : only(F.writeValue(at2));
      }
      case "highlight-incomplete": {
        const row2 = issue.row;
        return only(F.fillHighlight(issue.empty.map((h) => ref(tab, colOf(payload, tab, h), row2))));
      }
      case "type-invalid": {
        const computed = { when: F.siteComputes, action: F.put("COMPUTED", [at2]) };
        const custom = { when: F.ownHighlight, action: F.put("CUSTOM", [at2]) };
        return isComputed(field(payload, tab, issue.row, "CODE")) ? [computed, custom] : [custom, computed];
      }
      case "highlight-unknown": {
        const meant = HIGHLIGHT_CODES.filter((c) => editDistance(c, issue.found.toUpperCase()) <= 1);
        const type = ref(tab, colOf(payload, tab, "TYPE"), issue.row);
        return [
          ...meant.map((c) => ({ when: F.wasComputed(c), action: F.put(c, [at2]) })),
          { when: F.ownHighlight, action: F.makeCustom(type) }
        ];
      }
      case "unit-without-value":
        return [
          { when: F.hasFigure, action: F.writeValue(ref(tab, colOf(payload, tab, "VALUE"), issue.row)) },
          { when: F.noFigure, action: F.clear(at2) }
        ];
      case "highlight-reserved":
        return [
          { when: F.siteComputes, action: F.makeComputed(ref(tab, colOf(payload, tab, "TYPE"), issue.row)) },
          { when: F.ownHighlight, action: F.renameCode(at2) }
        ];
      // SPONSORS.
      case "sponsors-missing":
        return only(F.createSponsors);
      case "sponsors-none":
        return only(F.addSponsors(issue.season));
      case "logo-unknown": {
        const base = (s) => s.toLowerCase().replace(/.[a-z]+$/, "");
        const close = Object.keys(payload.sponsorLogos ?? {}).filter(
          (file) => issue.found !== "" && editDistance(base(file), base(issue.found)) <= 2
        );
        return [
          ...close.map((file) => ({ when: F.logoIs(file), action: F.put(file, [at2]) })),
          { when: F.logoNew, action: F.askLogo(field(payload, tab, issue.row, "NAME")) }
        ];
      }
      case "tier-invalid":
        return only(F.writeTier(at2));
      case "since-invalid":
      case "since-after":
        return [
          { when: F.sinceKnown, action: F.writeSince(at2, field(payload, tab, issue.row, "SEASON")) },
          { when: F.sinceUnknown, action: F.clear(at2) }
        ];
      // PROMOTIONS.
      case "discipline-unknown":
      case "level-invalid": {
        const values = issue.code === "discipline-unknown" ? issue.disciplines : issue.levels;
        const meant = values.find((v) => v === issue.found.trim().toUpperCase());
        return meant ? [{ when: F.meantSpecial(meant), action: F.put(meant, [at2]) }] : only(F.writeOneOf(at2, values));
      }
      case "promotion-not-up": {
        const from = ref(tab, colOf(payload, tab, "FROM"), issue.row);
        return [
          ...issue.from === issue.to ? [] : [{ when: F.levelsSwapped, action: F.swapLevels(from, at2) }],
          { when: F.notPromoted, action: F.deleteRow(ref(tab, void 0, issue.row)) }
        ];
      }
      case "promotion-name-unknown":
      case "media-kit-name-unknown": {
        const names = new Set(
          rowsOf(payload, "CONFIG").filter((r, i) => i > 0 && r[colOf(payload, "CONFIG", "SEASON")] === issue.season).flatMap((r) => rowsOf(payload, r[colOf(payload, "CONFIG", "TAB")]).slice(1)).map((r) => r[NAME_COL] ?? "")
        );
        const close = [...names].filter(
          (n) => n !== "" && (riderKey(n) === riderKey(issue.found) || editDistance(n, issue.found) <= 2)
        );
        return [
          ...close.map((name2) => ({ when: F.riderIs(name2), action: F.put(name2, [at2]) })),
          { when: F.riderNotFound(issue.season), action: F.checkName(at2) }
        ];
      }
      case "promotion-no-discipline":
        return [
          { when: F.disciplineWrong, action: F.checkDiscipline(ref(tab, colOf(payload, tab, "DISCIPLINE"), issue.row)) },
          { when: F.wanted, action: F.ignore }
        ];
      // DISTANCES.
      case "distances-missing":
        return only(F.createDistances);
      case "distance-none":
        return only(F.addDistance(issue.from, issue.to));
      case "distance-venue-unknown":
        return [
          { when: F.venueTypo, action: F.writeVenueCode(at2) },
          { when: F.venueNew, action: F.addVenueRow(issue.found) }
        ];
      case "distance-same":
        return only(F.writeVenueCode(at2));
      case "distance-invalid": {
        const m = /^(\d[\d\s]*?)\s*[^\d.,]*$/.exec(issue.found);
        const value = m ? m[1].replace(/\s/g, "") : "";
        return value ? [{ when: F.valueIs(value), action: F.put(value, [at2]) }] : only(F.writeValue(at2));
      }
      // Between the tabs.
      case "column-no-calendar": {
        const season = ctx.season;
        const row2 = rowsOf(payload, "CALENDAR").findIndex(
          (_, i) => i > 0 && field(payload, "CALENDAR", i + 1, "SEASON") === season && field(payload, "CALENDAR", i + 1, "EVENT") === issue.found
        );
        return row2 > 0 ? [{ when: F.calendarRowIgnored(row2 + 1), action: F.seeCalendar }] : [
          { when: F.calendarRowMissing, action: F.addCalendarRow(season, issue.found, codeOf(issue.tab, place2)) },
          { when: F.columnTypo, action: F.renameColumn(ref(issue.tab, issue.headerCol, 1)) }
        ];
      }
      case "calendar-no-column": {
        const code = codeOf(issue.tab, place2);
        return [
          { when: F.countsFor(code), action: F.addColumn(issue.found, ref(issue.tab)) },
          {
            when: F.notFor(code),
            action: F.removeCode(code, ref("CALENDAR", colOf(payload, "CALENDAR", "CHAMPIONSHIPS"), issue.row))
          }
        ];
      }
      case "column-order":
        return only(F.reorder(ref(issue.tab), issue.expected));
      case "name-similar":
      case "name-spelling":
        return [
          ...issue.variants.map((good) => ({
            when: F.sameRider(good.name),
            action: F.rename(
              good.name,
              issue.variants.filter((v) => v !== good).flatMap((v) => v.cells.map((c) => ref(c.tab, c.col, c.row)))
            )
          })),
          ...issue.code === "name-similar" ? [{ when: F.twoRidersSimilar(issue.variants.map((v) => v.name)), action: F.ignore }] : []
        ];
      case "class-spelling":
      case "event-spelling":
        return issue.variants.map((good) => ({
          when: F.rightName(good.name),
          action: F.writeIn(
            good.name,
            issue.variants.filter((v) => v !== good).flatMap((v) => v.places)
          )
        }));
    }
  }
  function valueFixes(found, at2) {
    const F = T.cards.fix;
    const fixes = [];
    const digits = found.replace(/[oO]/g, "0").replace(/[lI]/g, "1").trim();
    if (digits !== found && /^\d+$/.test(digits) && POINTS_SCALE.includes(Number(digits)))
      fixes.push({ when: F.meantPoints(digits), action: F.put(digits, [at2]) });
    const upper = found.trim().toUpperCase();
    if (upper !== found && ["DNF", "DNS", "X", "N/A"].includes(upper))
      fixes.push({ when: F.meantSpecial(upper), action: F.put(upper, [at2]) });
    fixes.push({ when: F.otherValue, action: F.allowed(at2) });
    return fixes;
  }

  // plugins/csm-standings/src/site/format.ts
  function esc(s) {
    return String(s).replace(
      /[&<>"]/g,
      (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[ch]
    );
  }
  function fold(s) {
    return s.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toUpperCase();
  }
  var byName = (a, b) => a.localeCompare(b, "fr");
  var dateRange3 = (e) => T.dateRange(e);
  function raceDaysText(e) {
    return e.raceDays.length === 0 ? T.dateRange(e) : T.raceDays(e.raceDays);
  }
  function stateOf(e, today) {
    return e.end < today ? "past" : e.start <= today ? "live" : "upcoming";
  }
  function daysUntil(date, today) {
    return Math.round((Date.parse(date) - Date.parse(today)) / 864e5);
  }
  var ord = (n) => T.ord(n);
  var ordText = (n) => T.ordText(n);
  var medal = (n) => n <= 3 ? "m" + String(n) : "";
  var CALENDAR_ICON = '<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><rect x="2" y="3" width="12" height="11" rx="2" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M2 6.5h12M5.5 1.5v3M10.5 1.5v3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>';
  function schedIcon(e) {
    if (!e.schedule) return "";
    const label2 = esc(T.scheduleLabel(e.event));
    return '<a class="sched" href="' + esc(e.schedule) + '" target="_blank" rel="noopener" aria-label="' + label2 + '" title="' + label2 + '">' + CALENDAR_ICON + "</a>";
  }
  function galaWhen(text) {
    if (!isPublicDate(text)) return null;
    const [y, m, d] = text.slice(0, 10).split("-");
    return T.gala.when(Number(d), Number(m), y, text.length > 10 ? text.slice(11) : null);
  }

  // plugins/csm-standings/src/site/model.ts
  var totalOf = (r) => Number(r[COLUMN_NET_TOTAL] ?? r[COLUMN_TOTAL]);
  function tiebreakOfClass(c, k) {
    const results2 = k.riders.map((row2) => c.events.map((ev) => resultOf(row2[ev])));
    return tiebreakOf(results2, c.events.length, c.scale, 0, 1);
  }
  var hasResults = (c, ev) => c.classes.some((k) => k.riders.some((r) => r[ev] !== "" && r[ev] !== "N/A"));
  var NUMBER2 = /^-?\d+(\.\d+)?$/;
  var participated = (r, events) => events.some((ev) => {
    const v = r[ev];
    return NUMBER2.test(v) || v === "DNF" || v === "DNS";
  });
  function shownClass(k, events) {
    const riders = k.riders.filter((r) => participated(r, events));
    return riders.length ? { ...k, riders } : k;
  }
  var raceRun = (c, ev) => c.entered[c.events.indexOf(ev)];
  function seasonRaces(model) {
    const done = model.calendar.filter((e) => {
      const champs = e.codes.map((code) => model.byCode.get(code)).filter((c) => !c.blocked && c.events.includes(e.event));
      return champs.length > 0 && champs.every((c) => raceRun(c, e.event));
    }).length;
    return { done, total: model.calendar.length };
  }
  function calFor(model, code, event) {
    return model.calendar.find((e) => e.event === event && e.codes.includes(code));
  }
  function modelOf(data, today) {
    const model = {
      season: data.season,
      rules: data.rules,
      today,
      champs: [],
      byCode: /* @__PURE__ */ new Map(),
      calendar: data.calendar,
      people: /* @__PURE__ */ new Map(),
      gala: data.gala,
      highlights: data.highlights,
      sponsors: data.sponsors,
      promotions: data.promotions,
      distances: data.distances,
      mediaKit: data.mediaKit
    };
    for (const { entry, standings, blocked, events, entered } of data.championships) {
      const classes = (standings?.classes ?? []).map((k) => shownClass(k, events));
      const races = events.map((ev) => data.calendar.find((e) => e.event === ev && e.codes.includes(entry.code)));
      const champ = {
        code: entry.code,
        name: entry.name,
        color: entry.color,
        logo: entry.logo,
        rulebook: entry.rulebook,
        rulebookUrl: entry.rulebookUrl,
        scale: entry.championship ? pointsScaleOf(entry.championship) : POINTS_SCALE,
        events,
        classes,
        blocked,
        entered,
        done: entered.filter(Boolean).length,
        live: races.some((e) => e !== void 0 && stateOf(e, today) === "live"),
        total: events.length,
        // The riders its classes show (before a class's first race, its riders as listed); one in several classes counts once.
        riders: new Set(classes.flatMap((k) => k.riders.map((r) => r[COLUMN_NAME]))).size
      };
      model.champs.push(champ);
      model.byCode.set(champ.code, champ);
      for (const klass of classes) {
        for (const row2 of klass.riders) {
          const name2 = row2[COLUMN_NAME];
          let list3 = model.people.get(name2);
          if (!list3) model.people.set(name2, list3 = []);
          list3.push({
            code: champ.code,
            cls: klass.name,
            rank: Number(row2[COLUMN_RANK]),
            of: klass.riders.length,
            pts: totalOf(row2),
            no: row2[COLUMN_PLATE],
            row: row2,
            klass
          });
        }
      }
    }
    return model;
  }
  function byOrder2(model) {
    const order = (code) => model.champs.findIndex((c) => c.code === code);
    const place2 = (e) => model.byCode.get(e.code).classes.findIndex((k) => k.name === e.cls);
    return (a, b) => order(a.code) - order(b.code) || place2(a) - place2(b);
  }
  function entriesOf(model, name2) {
    return model.people.get(name2).slice().sort(byOrder2(model));
  }
  function groupsOf(model, list3, focus) {
    const groups = /* @__PURE__ */ new Map();
    for (const e of list3) {
      let g = groups.get(e.code);
      if (!g) groups.set(e.code, g = []);
      g.push(e);
    }
    const order = (code) => model.champs.findIndex((c) => c.code === code);
    return [...groups].sort(([a], [b]) => Number(b === focus) - Number(a === focus) || order(a) - order(b));
  }

  // plugins/csm-standings/src/engine/title.ts
  function titleRace(className, riders, events, championship2) {
    if (!riders.length) return null;
    const left = events.flatMap((_, e) => riders.every((r) => r.results[e] === "") ? [e] : []);
    if (!left.length) return { kind: "final", champion: riders[0].name };
    const scale = pointsScaleOf(championship2);
    const win = scale[0];
    const run = (value) => {
      const sim = riders.map((r, i) => ({
        name: r.name,
        results: r.results.map((v, e) => left.includes(e) ? value(i) : v)
      }));
      const { totals, order } = rankOnEvents(className, sim, events, events.length, championship2);
      return { first: order[0], total: (i) => totals[i].joker?.net ?? totals[i].overall };
    };
    const duel = (a, va, b, vb) => run((i) => i === a ? va : i === b ? vb : ABSENT).first;
    const contenders = [];
    let out = 0;
    for (let i = 1; i < riders.length; i++) {
      const best2 = run((j) => j === i ? win : ABSENT);
      if (best2.first === i)
        contenders.push({ index: i, name: riders[i].name, max: best2.total(i), ifLeader: null, withoutWin: false });
      else out++;
    }
    const clinched2 = contenders.length === 0;
    let leaderNeeds = null;
    if (left.length === 1 && !clinched2) {
      for (let p = 0; p < scale.length; p++) {
        if (!contenders.every((c) => duel(0, scale[p], c.index, p === 0 ? scale[1] : win) === 0)) break;
        leaderNeeds = p + 1;
      }
      if (leaderNeeds === scale.length && [0, ...RACED_WITHOUT_POINTS].every((v) => contenders.every((c) => duel(0, v, c.index, win) === 0)))
        leaderNeeds = "entry";
      for (const c of contenders) {
        const wins = (v) => duel(c.index, win, 0, v) === c.index;
        if (wins(scale[1])) continue;
        let from = null;
        if (wins(0)) for (let p = scale.length - 1; p >= 1 && wins(scale[p]); p--) from = p + 1;
        const [dnf, dns] = RACED_WITHOUT_POINTS.map(wins);
        c.ifLeader = { from, out: wins(0), dnf, dns };
        const notRacing = [dnf ? "DNF" : "", dns ? "DNS" : "", ABSENT].filter((v) => v !== "");
        c.withoutWin = from === null && !c.ifLeader.out && notRacing.every((v) => duel(c.index, scale[1], 0, v) === c.index);
      }
    }
    return {
      kind: "open",
      left: left.map((e) => events[e]),
      leader: riders[0].name,
      clinched: clinched2,
      leaderNeeds,
      contenders,
      out
    };
  }

  // plugins/csm-standings/src/site/views/common.ts
  var who = (name2, cls = "") => '<button type="button" class="who ' + cls + '" data-person="' + esc(name2) + '">' + esc(name2) + "</button>";
  function chip(model, code) {
    const c = model.byCode.get(code);
    return '<span class="chip" style="--c:' + esc(c.color) + '">' + esc(code) + "</span>";
  }
  var logo = (c, alt = "") => c.logo ? '<img src="' + esc(c.logo) + '" alt="' + esc(alt) + '">' : "";
  var notice = () => '<div class="notice">' + esc(T.updating) + "</div>";
  var pts = (n) => '<span class="pts">' + String(n) + "<small>" + T.pts + "</small></span>";
  var rankOrd = (n) => '<span class="ord ' + medal(n) + '">' + ord(n) + "</span>";
  var bigPts = (n) => '<span class="bpts">' + String(n) + "<small>" + T.pts + "</small></span>";
  var phaseOf = (done, total) => done === total ? "final" : done === 0 ? "upcoming" : "live";
  var champPhase = (c) => c.blocked ? "blocked" : phaseOf(c.done, c.total);
  var statusHtml = (phase, label2, detail = "") => '<span class="status"><span class="spill ' + phase + '">' + esc(label2) + "</span>" + (detail ? " " + esc(detail) : "") + "</span>";
  function champStatusParts(c) {
    const phase = champPhase(c);
    const weekend = c.live ? T.raceThisWeekend : "";
    if (phase === "final") return { phase, label: T.finalStandings, detail: "" };
    if (phase === "live") return { phase, label: T.phase.live, detail: T.inProgress(c.done, c.total) + weekend };
    if (phase === "upcoming") return { phase, label: T.phase.upcoming, detail: T.racesPlanned(c.total) + weekend };
    return { phase, label: T.phase.blocked, detail: "" };
  }
  function champStatus(c) {
    const s = champStatusParts(c);
    return statusHtml(s.phase, s.label, s.detail);
  }
  var eventAttrs = (e) => ' data-event="' + esc(e.event) + '" data-start="' + esc(e.start) + '"';
  var eventLink = (e, text) => '<button type="button" class="evlink"' + eventAttrs(e) + ">" + esc(text) + "</button>";

  // plugins/csm-standings/src/site/views/bar.ts
  var CHEVRON = '<svg viewBox="0 0 14 9" width="14" height="9" aria-hidden="true"><path d="M1 1l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>';
  var OVERVIEW_ICON = '<svg class="ovi" viewBox="0 0 24 24" width="30" height="30" aria-hidden="true"><rect x="3" y="3" width="7.5" height="7.5" rx="1.6"/><rect x="13.5" y="3" width="7.5" height="7.5" rx="1.6"/><rect x="3" y="13.5" width="7.5" height="7.5" rx="1.6"/><rect x="13.5" y="13.5" width="7.5" height="7.5" rx="1.6"/></svg>';
  function header(uid, seasons, season) {
    const id = uid + "-season";
    return '<div class="top"><div><p class="eyebrow">' + esc(T.eyebrow) + '</p><div class="ttl"><h1>' + esc(T.title) + '</h1><span class="ypick"><label class="vh" for="' + esc(id) + '">' + esc(T.seasonLabel) + '</label><select id="' + esc(id) + '" class="ysel" data-season="1">' + seasons.map((s) => '<option value="' + esc(s) + '"' + (s === season ? " selected" : "") + ">" + esc(s) + "</option>").join("") + '</select><button type="button" class="ybtn" aria-haspopup="listbox" aria-expanded="false" aria-label="' + esc(T.seasonPick(season)) + '"><span>' + esc(season) + "</span>" + CHEVRON + '</button><div class="ymenu" role="listbox" aria-label="' + esc(T.seasonLabel) + '" hidden><p class="yh" aria-hidden="true">' + esc(T.seasonLabel) + "</p>" + seasons.slice().sort((a, b) => b.localeCompare(a)).map(
      (s) => '<button type="button" role="option" class="yopt" data-yopt="' + esc(s) + '" aria-selected="' + String(s === season) + '" tabindex="' + (s === season ? "0" : "-1") + '"><b>' + esc(s) + "</b>" + (s === season ? '<span class="ok" aria-hidden="true">✓</span>' : "") + "</button>"
    ).join("") + '</div></span></div></div><div class="gslot"></div></div>';
  }
  function seasonPhase(model, done) {
    const shown2 = model.champs.filter((c) => !c.blocked);
    if (!shown2.length) return "blocked";
    if (shown2.every((c) => champPhase(c) === "final")) return "final";
    return done === 0 ? "upcoming" : "live";
  }
  var tileText = (phase, done, total, final) => phase === "final" ? final : phase === "live" ? T.tileLive(done, total) : T.phase[phase];
  var pill = (phase, text) => '<span class="spill ' + phase + '">' + esc(text) + "</span>";
  function bar(model, sel) {
    const races = seasonRaces(model);
    const sp = seasonPhase(model, races.done);
    const S = T.seasonStatus;
    const seasonSays = sp === "live" ? S.live(races.done, races.total) : sp === "final" ? S.final : sp === "upcoming" ? S.upcoming : S.blocked;
    return '<button type="button" class="tile overview" data-sel="overview" aria-pressed="' + String(sel === "overview") + '" aria-label="' + esc(T.tileAria(T.seasonAria(model.season), seasonSays)) + '">' + OVERVIEW_ICON + '<span class="code">' + esc(T.overviewTile) + "</span>" + pill(sp, tileText(sp, races.done, races.total, T.seasonDone)) + "</button>" + model.champs.map((c) => {
      const s = champStatusParts(c);
      return '<button type="button" class="tile" data-sel="' + esc(c.code) + '" style="--c:' + esc(c.color) + '" aria-pressed="' + String(sel === c.code) + '" aria-label="' + esc(T.tileAria(c.name, s.detail ? s.label + ", " + s.detail : s.label)) + '">' + (c.logo ? '<img src="' + esc(c.logo) + '" alt="">' : "") + '<span class="code">' + esc(c.code) + "</span>" + pill(s.phase, tileText(s.phase, c.done, c.total, T.phase.final)) + "</button>";
    }).join("");
  }

  // plugins/csm-standings/src/site/views/place.ts
  var mapUrl = (point) => "https://www.google.com/maps/search/?api=1&query=" + point;
  var PIN = '<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><path d="M8 14.5s4.5-4.3 4.5-8a4.5 4.5 0 10-9 0c0 3.7 4.5 8 4.5 8z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><circle cx="8" cy="6.5" r="1.7" fill="none" stroke="currentColor" stroke-width="1.6"/></svg>';
  function mapIcon(e) {
    const point = e.place?.point;
    if (!point) return "";
    const label2 = esc(T.place.map(e.venue));
    return '<a class="sched map" href="' + esc(mapUrl(point)) + '" target="_blank" rel="noopener" aria-label="' + label2 + '" title="' + label2 + '">' + PIN + "</a>";
  }
  var atPlace = (e, pin) => e.venue ? " · " + esc(e.venue) + (pin ? mapIcon(e) : "") : "";
  function venueBlock(e) {
    const place2 = e.place;
    if (!place2) return "";
    const body = PIN + "<span><b>" + esc(e.venue) + "</b>" + (place2.address ? "<small>" + esc(place2.address) + "</small>" : "") + "</span>";
    return place2.point ? '<a class="eplace" href="' + esc(mapUrl(place2.point)) + '" target="_blank" rel="noopener">' + body + '<span class="go" aria-hidden="true">' + esc(T.place.mapLink) + "</span></a>" : '<div class="eplace">' + body + "</div>";
  }

  // plugins/csm-standings/src/site/views/banner.ts
  function bannerHtml(b) {
    return '<div class="banner nologo ub" style="--c:' + b.color + '"><div class="ub-top"><div class="grow"><p class="t">' + esc(b.title) + '</p><div class="meta">' + b.status + b.link + (b.figures.length ? '<span class="ub-figs">' + esc(b.figures.join(" · ")) + "</span>" : "") + "</div></div>" + b.side + "</div>" + b.bottom + "</div>";
  }
  function seasonBanner(model, side, quit) {
    const races = seasonRaces(model);
    const phase = seasonPhase(model, races.done);
    const classes = new Set(model.champs.flatMap((c) => c.classes.map((k) => k.name))).size;
    const rules = esc(T.rulebookLink(model.rules.rulebook));
    const url2 = model.rules.rulebookUrl;
    return bannerHtml({
      title: T.banner.season(model.season),
      color: "var(--csm-ink)",
      status: statusHtml(phase, phase === "final" ? T.seasonDone : T.phase[phase]),
      link: url2 ? '<a class="link" href="' + esc(url2) + '" target="_blank" rel="noopener">' + rules + "</a>" : "<span>" + rules + "</span>",
      figures: [
        T.championships(model.champs.length),
        T.categories(classes),
        T.riders(model.people.size),
        T.banner.races(races.done, races.total)
      ],
      bottom: quit ? '<div class="ub-quit">' + quit + "</div>" : "",
      side
    });
  }
  function nextRow(model, code) {
    const e = model.calendar.find((r) => r.codes.includes(code) && stateOf(r, model.today) !== "past");
    if (!e) return "";
    const when = stateOf(e, model.today) === "live" ? T.liveNow : T.inDays(daysUntil(e.start, model.today));
    return '<div class="nextrow go"' + eventAttrs(e) + ' role="link" tabindex="0" aria-label="' + esc(T.event.open(e.event, dateRange3(e))) + '"><span class="k">' + esc(T.nextRace) + "</span><b>" + esc(e.event) + "</b><span>" + dateRange3(e) + atPlace(e, true) + " · " + esc(when) + "</span>" + schedIcon(e) + '<span class="chev" aria-hidden="true">›</span></div>';
  }
  function champBanner(model, c) {
    const s = champStatusParts(c);
    return bannerHtml({
      title: c.name + " " + model.season,
      color: esc(c.color),
      status: c.blocked ? "" : statusHtml(s.phase, s.label),
      link: "",
      figures: c.blocked ? [] : [T.categories(c.classes.length), T.riders(c.riders), T.banner.races(c.done, c.total)],
      bottom: nextRow(model, c.code),
      side: ""
    });
  }

  // plugins/csm-standings/src/site/views/calendar.ts
  var opens = (e) => eventAttrs(e) + ' role="link" tabindex="0" aria-label="' + esc(T.event.open(e.event, dateRange3(e))) + '"';
  function calendarOf(model, rows3, title, tags2) {
    const today = model.today;
    const firstUpcoming = rows3.findIndex((e) => stateOf(e, today) === "upcoming");
    const liveTag = '<span class="tag live">' + esc(T.live) + "</span>";
    return '<div class="section-h"><h2>' + esc(title) + '</h2><span class="aside">' + esc(T.calendarAside) + '</span></div><div class="cal' + (tags2 ? "" : " notags") + '">' + rows3.map((e, i) => {
      const st = stateOf(e, today);
      const tag = st === "live" ? liveTag : i === firstUpcoming ? '<span class="tag next">' + esc(T.nextTag) + "</span>" : "";
      return '<div class="cal-item ' + st + (i === firstUpcoming ? " first" : "") + '"' + opens(e) + '><div class="when"><span class="dt">' + dateRange3(e) + "</span>" + tag + schedIcon(e) + '</div><span class="ev">' + esc(e.event) + '</span><span class="ve">' + esc(e.venue) + mapIcon(e) + "</span>" + (tags2 ? '<div class="chips">' + e.codes.map((code) => chip(model, code)).join("") + "</div>" : "") + "</div>";
    }).join("") + "</div>";
  }
  function scrollCalendars(root) {
    for (const cal of root.querySelectorAll(".cal")) {
      const items = [...cal.querySelectorAll(".cal-item")];
      const next = items.findIndex((x) => !x.classList.contains("past"));
      const last = (next < 0 ? items.length : next) - 1;
      if (last > 0) cal.scrollLeft += items[last].getBoundingClientRect().left - cal.getBoundingClientRect().left;
    }
  }

  // plugins/csm-standings/src/site/views/joker.ts
  function dropped(r) {
    const event = r[COLUMN_JOKER];
    if (!event) return null;
    return {
      event,
      value: r[event],
      pts: Number(r[COLUMN_TOTAL]) - Number(r[COLUMN_NET_TOTAL])
    };
  }
  var droppedCell = (inner) => '<span class="jcell" title="' + esc(T.jokerHelp.dropped) + '"><span class="jk">' + inner + "</span></span>";
  function ruleLink(c, text) {
    return c.rulebookUrl ? '<a class="jlink" href="' + esc(c.rulebookUrl) + '" target="_blank" rel="noopener">' + esc(text) + " ↗</a>" : '<span class="jlink">' + esc(text) + "</span>";
  }

  // plugins/csm-standings/src/site/views/legend.ts
  var legendButton = (key) => '<button type="button" class="legend-btn" data-help="' + key + '" aria-haspopup="dialog"><span class="q" aria-hidden="true">?</span>' + esc(T.legendButton) + "</button>";
  function legendDialog(key, title, intro, items) {
    return '<dialog class="help hlegend" data-help-for="' + key + '" aria-labelledby="' + key + '-t"><div class="h-h"><b id="' + key + '-t">' + esc(title) + '</b><button type="button" class="h-x" data-help-close="1" aria-label="' + esc(T.close) + '">×</button></div><div class="h-b"><p>' + esc(intro) + "</p><dl>" + items.map(([sample, text]) => "<div><dt>" + sample + "</dt><dd>" + esc(text) + "</dd></div>").join("") + "</dl></div></dialog>";
  }

  // plugins/csm-standings/src/site/views/classements.ts
  var NUMBER3 = /^-?\d+(\.\d+)?$/;
  var name = (r) => r[COLUMN_NAME];
  var plate = (r) => r[COLUMN_PLATE];
  function classStatus(c, k) {
    const held = (ev) => k.riders.some((r) => r[ev] !== "" && r[ev] !== "N/A");
    const done = c.events.filter(held).length;
    const planned = c.events.filter((ev) => !k.riders.every((r) => r[ev] === "N/A")).length;
    const phase = phaseOf(done, planned);
    if (phase === "final") return { phase, label: T.classFinal, detail: "" };
    return { phase, label: T.phase[phase], detail: T.classProgress(done, planned) };
  }
  function classStatusHtml(c, k, more) {
    const s = classStatus(c, k);
    return statusHtml(s.phase, s.label, [s.detail, more].filter(Boolean).join(" · "));
  }
  function tieReason(c, k) {
    const t = tiebreakOfClass(c, k);
    if (t.kind === "place") return t.place === 1 ? T.tieBy.wins(t.a, t.b) : T.tieBy.places(t.place, t.a, t.b);
    if (t.kind === "outside") return T.tieBy.outside(t.a, t.b);
    if (t.kind === "event") return (t.last ? T.tieBy.last : T.tieBy.earlier)(c.events[t.event]);
    return T.tieBy.name;
  }
  function gapText(k, i, c) {
    const r = k.riders;
    if (i === 0) {
      if (r.length < 2) return T.onlyRider;
      const lead = totalOf(r[0]) - totalOf(r[1]);
      if (lead !== 0) return T.leadBy(lead);
      return c ? T.tiedLeadBy(tieReason(c, k)) : T.tiedLead;
    }
    const d = totalOf(r[0]) - totalOf(r[i]);
    return d === 0 ? T.tied : T.behind(d);
  }
  function cellHtml(v) {
    if (v === "") return "";
    if (v === "N/A") return '<span class="na">' + T.noValue + "</span>";
    if (NUMBER3.test(v)) return esc(v);
    return '<span class="' + (v === "X" ? "ab" : "sp") + '">' + esc(v) + "</span>";
  }
  function weight(v) {
    if (NUMBER3.test(v)) return Number(v);
    return { DNF: -1, DNS: -2, X: -3, "N/A": -4 }[v] ?? -5;
  }
  function openClass(c, state) {
    return c.classes.find((k) => k.name === state.cls[c.code]) ?? null;
  }
  function arrow(side) {
    return '<button type="button" class="tarrow ' + side + '" data-scroll="' + (side === "l" ? "-1" : "1") + '" aria-label="' + esc(side === "l" ? T.prevClasses : T.nextClasses) + '">' + (side === "l" ? "‹" : "›") + "</button>";
  }
  function filters(c, state) {
    const sel = state.filter[c.code] ?? [];
    return '<div class="filters"><div class="fwrap">' + arrow("l") + '<div class="fscroll">' + c.classes.map(
      (k) => '<button type="button" class="fchip" data-filter="' + esc(k.name) + '" aria-pressed="' + String(sel.includes(k.name)) + '">' + esc(k.name) + "</button>"
    ).join("") + "</div>" + arrow("r") + "</div>" + (sel.length ? '<button type="button" class="fclear" data-filter="">' + esc(T.clearAll) + "</button>" : "") + "</div>";
  }
  function card(c, k) {
    const head = '<header><div class="kh"><h3>' + esc(k.name) + "</h3></div>" + badge(c, k) + "</header>";
    if (k.blocked) return '<article class="kcard">' + head + notice() + "</article>";
    const [a, ...rest] = k.riders.slice(0, 3);
    return '<article class="kcard" data-open="' + esc(k.name) + '" role="link" tabindex="0">' + head + '<div class="kc-lead">' + rankOrd(1) + '<div class="wbox">' + who(name(a), "rname") + '<span class="subl">' + T.plate + " " + esc(plate(a)) + " · " + esc(gapText(k, 0, c)) + "</span></div>" + bigPts(totalOf(a)) + '</div><ol class="kc-chase">' + rest.map(
      (r, i) => "<li>" + rankOrd(i + 2) + who(name(r), "rname") + '<span class="gapt">' + esc(gapText(k, i + 1)) + '</span><span class="cpts">' + String(totalOf(r)) + "</span></li>"
    ).join("") + "</ol><footer>" + classStatusHtml(c, k, T.riders(k.riders.length)) + '<span class="more-t">' + esc(T.standingsLink) + "</span></footer></article>";
  }
  function classCards(model, c, state) {
    const sel = state.filter[c.code] ?? [];
    const shown2 = sel.length ? c.classes.filter((k) => sel.includes(k.name)) : c.classes;
    return titleStrip(model, c) + '<div class="fline">' + filters(c, state) + '</div><div class="cards">' + shown2.map((k) => card(c, k)).join("") + "</div>";
  }
  function podiumTie(c, k, i) {
    if (i > 0 || k.riders.length < 2 || totalOf(k.riders[0]) !== totalOf(k.riders[1])) return "";
    return '<p class="ktie">' + esc(T.tieWhy(tieReason(c, k))) + "</p>";
  }
  function podium(c, k) {
    return '<div class="kpod">' + k.riders.slice(0, 3).map(
      (r, i) => '<div class="kstep p' + String(i + 1) + '">' + rankOrd(i + 1) + '<span class="kof">' + esc(T.ofN(k.riders.length)) + "</span>" + who(name(r), "rname") + '<span class="subl">' + T.plate + " " + esc(plate(r)) + '</span><div class="kline">' + bigPts(totalOf(r)) + '<span class="gapt">' + esc(gapText(k, i)) + "</span></div>" + podiumTie(c, k, i) + "</div>"
    ).join("") + "</div>";
  }
  function tabs(c, cur) {
    return '<div class="tabs-wrap">' + arrow("l") + '<div class="tabs" role="tablist">' + c.classes.map(
      (x) => '<button type="button" role="tab" class="' + (x.blocked ? "blk" : "") + '" data-cls="' + esc(x.name) + '" aria-selected="' + String(x.name === cur) + '">' + esc(x.name) + "</button>"
    ).join("") + "</div>" + arrow("r") + "</div>";
  }
  function table(model, c, k, state) {
    const joker = k.headers.includes(COLUMN_JOKER);
    const cols = [
      [COLUMN_RANK, T.columns.rank, "rk sticky s1"],
      [COLUMN_PLATE, T.columns.num, "sticky s2"],
      [COLUMN_NAME, T.columns.name, "nm sticky s3"],
      ...c.events.map((ev) => [ev, ev, "ev"]),
      ...joker ? [
        [COLUMN_TOTAL, T.columns.gross, "gross"],
        [COLUMN_NET_TOTAL, T.columns.total, "tot"]
      ] : [[COLUMN_TOTAL, T.columns.total, "tot"]]
    ];
    const rows3 = k.riders.slice();
    const sort = state.sort;
    if (sort) {
      const cell = (r) => r[sort.key];
      rows3.sort((a, b) => {
        const d = sort.key === COLUMN_NAME ? cell(b).localeCompare(cell(a), "fr") : weight(cell(b)) - weight(cell(a));
        return sort.dir === "ascending" ? -d : d;
      });
    }
    const lead = totalOf(k.riders[0]);
    const head = cols.map(([key, label2, cls]) => {
      const e = c.events.includes(key) ? calFor(model, c.code, key) : void 0;
      const aria = sort?.key === key ? ' aria-sort="' + sort.dir + '"' : "";
      return '<th class="' + cls.replace(/\b(rk|nm|tot|gross|ev)\b/g, "").trim() + '" data-sort="' + esc(key) + '"' + aria + ">" + esc(label2) + (e ? '<span class="dt">' + raceDaysText(e) + "</span>" : "") + "</th>";
    }).join("") + '<th class="gapc">' + esc(T.columns.gap) + "</th>";
    const body = rows3.map((r) => {
      const rk = Number(r[COLUMN_RANK]);
      const d = lead - totalOf(r);
      return '<tr class="' + (rk <= 3 ? "r" + String(rk) : "") + '">' + cols.map(([key, , cls]) => {
        const v = r[key];
        let html = cls === "ev" ? cellHtml(v) : key === COLUMN_NAME ? who(v) : esc(v);
        if (joker && r[COLUMN_JOKER] === key) html = droppedCell(cellHtml(v));
        return '<td class="' + cls + '">' + html + "</td>";
      }).join("") + '<td class="gapc">' + (rk === 1 ? T.noValue : d === 0 ? T.tieGap : "−" + String(d)) + "</td></tr>";
    }).join("");
    return sectionHead(T.tableLegend.title, T.tableLegend.aside, legendButton(TABLE_LEGEND)) + '<div class="tscroll"><table><thead><tr>' + head + "</tr></thead><tbody>" + body + "</tbody></table></div>" + // The rule itself is the rulebook's: a link to its article, never a copy.
    (joker ? '<p class="tnote jnote">' + ruleLink(c, T.jokerHelp.link) + "</p>" : "") + '<p class="tnote">' + esc(T.sortHint) + "</p>" + tableLegend(joker);
  }
  var TABLE_LEGEND = "tlegend";
  function tableLegend(joker) {
    const L = T.tableLegend;
    const cells = (...vs3) => vs3.map((v) => '<span class="tcell">' + cellHtml(v) + "</span>").join("");
    const col = (...labels) => labels.map((l) => '<span class="tcol">' + esc(l) + "</span>").join("");
    return legendDialog(TABLE_LEGEND, L.legendTitle, L.intro + " " + T.sortHint, [
      [cells("25", "11"), L.points],
      [cells("0"), L.out],
      [cells("25"), T.legendNotRun],
      [cells("DNF", "DNS"), T.legendDnf],
      [cells("X"), T.legendAbsent],
      [cells("N/A"), L.na],
      [cells(""), T.legendFuture],
      ...joker ? [
        [droppedCell(cellHtml("16")), T.legendJoker],
        [col(T.columns.gross, T.columns.total), L.gross]
      ] : [[col(T.columns.total), L.total]],
      [col(T.columns.gap), L.gap]
    ]);
  }
  function classPage(model, c, k, state) {
    const top2 = '<button type="button" class="back" data-allcls="1">' + esc(T.allClasses) + "</button>" + tabs(c, k.name);
    if (k.blocked) return top2 + '<div class="detail-head"><h3>' + esc(k.name) + "</h3></div>" + notice();
    return top2 + '<div class="detail-head"><h3>' + esc(k.name) + "</h3>" + classStatusHtml(c, k, "") + "</div>" + podium(c, k) + titlePanel(model, c, k) + table(model, c, k, state);
  }

  // plugins/csm-standings/src/site/views/title.ts
  var cache = /* @__PURE__ */ new WeakMap();
  function titleOf(c, k) {
    let t = cache.get(k);
    if (t === void 0) {
      const championship2 = championshipOf(c.rulebook, c.code);
      t = k.blocked ? null : titleRace(
        k.name,
        k.riders.map((r) => ({
          name: r[COLUMN_NAME],
          results: c.events.map((ev) => resultOf(r[ev]))
        })),
        c.events,
        championship2
      );
      cache.set(k, t);
    }
    return t;
  }
  function openRaces(c) {
    return c.classes.flatMap((k) => {
      const t = titleOf(c, k);
      return t?.kind === "open" ? [{ k, t }] : [];
    });
  }
  function atRace(c, ev) {
    const here = openRaces(c).filter((o) => o.t.left.includes(ev));
    return { sure: here.filter((o) => o.t.clinched), open: here.filter((o) => !o.t.clinched) };
  }
  function stakeTile(k, t, attrs) {
    const R = T.titleRace;
    return '<button type="button" class="stake' + (t.clinched ? " sure" : "") + '" ' + attrs + "><b>" + esc(k.name) + "</b><span>" + esc(t.clinched ? R.sureLead(t.leader) : R.stakeLead(t.leader, t.contenders.length)) + "</span><em>" + esc(t.clinched ? gapText(k, 0) : t.left.length === 1 ? needsText(t.leaderNeeds) : R.racesLeft(t.left.length)) + "</em></button>";
  }
  function badge(c, k) {
    const t = titleOf(c, k);
    if (!t) return "";
    const R = T.titleRace;
    if (t.kind === "final") return '<span class="tbadge champ">' + esc(R.badgeFinal) + "</span>";
    if (t.clinched) return '<span class="tbadge sure">' + esc(R.badgeSure) + "</span>";
    return '<span class="tbadge open">' + esc(R.badgeOpen(t.contenders.length + 1)) + "</span>";
  }
  function stripHtml(p) {
    const R = T.titleRace;
    const head = p.open === 0 ? R.allSure : p.left === 1 ? R.decider(p.ev) : R.racesLeft(p.left);
    const where = p.e ? dateRange3(p.e) + atPlace(p.e, false) : "";
    const when = p.live ? ' · <span class="tlive">● ' + esc(T.liveNow) + "</span>" : p.days > 0 ? " · " + esc(R.inDays(p.days)) : "";
    const name2 = p.open === 0 || p.left > 1 ? esc(p.left > 1 ? R.nextIs : R.lastRace) + "<b>" + esc(p.ev) + "</b>" : "";
    const nums = (p.open ? "<span><b>" + String(p.open) + "</b>" + esc(R.openLabel(p.open)) + "</span>" : "") + (p.sure ? "<span><b>" + String(p.sure) + "</b>" + esc(R.sureLabel(p.sure)) + "</span>" : "");
    const body = '<div><span class="k">' + esc(R.title) + "</span><b>" + esc(head) + '</b><span class="tsub">' + name2 + (name2 && where ? " · " : "") + where + when + '</span></div><div class="tnums">' + nums + "</div>";
    return p.e ? '<div class="tstrip go"' + eventAttrs(p.e) + ' role="link" tabindex="0" aria-label="' + esc(R.stakesTitle(p.ev)) + '">' + body + '<span class="tgo">' + esc(R.stripGo) + ' <span aria-hidden="true">›</span></span></div>' : '<div class="tstrip">' + body + "</div>";
  }
  function titleStrip(model, c) {
    const open = openRaces(c);
    if (!open.length) return "";
    const left = open[0].t.left;
    const sure = open.filter((o) => o.t.clinched).length;
    const e = calFor(model, c.code, left[0]);
    return stripHtml({
      ev: left[0],
      e,
      left: left.length,
      open: open.length - sure,
      sure,
      live: e !== void 0 && stateOf(e, model.today) === "live",
      days: e ? daysUntil(e.start, model.today) : 0
    });
  }
  function raceName(model, c, ev) {
    const e = calFor(model, c.code, ev);
    return "<b>" + esc(ev) + "</b>" + (e ? " (" + dateRange3(e) + ")" : "");
  }
  function needsText(needs) {
    const R = T.titleRace;
    if (needs === "entry") return R.needsEntry;
    if (needs === 1) return R.needsWin;
    return needs === null ? R.needsNothing : R.needsPlace(ordText(needs));
  }
  function leadSentence(model, k, t) {
    const R = T.titleRace;
    const w = who(t.leader, "rname");
    const race = t.left.length === 1 ? esc(t.left[0]) : null;
    if (t.clinched) return R.clinched(w, esc(k.name + " " + model.season), race);
    if (race === null) return R.leads(w, t.contenders.length);
    const n = t.leaderNeeds;
    if (n === "entry") return R.entry(w, race);
    if (n === 1) return R.win(w, race);
    return n === null ? R.notEnough(w) : R.place(w, ordText(n), race);
  }
  function condition(t, c) {
    const R = T.titleRace;
    if (t.left.length > 1) return R.inContention;
    const race = t.left[0];
    const w = c.ifLeader;
    if (w === null) return R.ifWin(race);
    const notRacing = w.dns ? [R.leaderNotRacing] : [R.leaderAbsent];
    const what = [
      ...w.from !== null ? [R.leaderPlace(ordText(w.from))] : w.out ? [R.leaderOut] : [],
      ...w.dnf ? [R.leaderDnf] : [],
      ...notRacing
    ];
    return w.from === null && !w.out ? R.ifLeaderNotRacing(t.leader, R.orList(what), race, c.withoutWin) : R.ifLeaderFinishes(t.leader, R.orList(what));
  }
  function titlePanel(model, c, k) {
    const t = titleOf(c, k);
    const R = T.titleRace;
    const head = '<div class="th"><span class="k">' + esc(R.title) + "</span>" + badge(c, k);
    if (t.kind === "final")
      return '<section class="tpanel">' + head + '</div><p class="tlead">' + rankOrd(1) + "<span>" + R.final(who(t.champion, "rname"), esc(k.name + " " + c.code + " " + model.season)) + "</span></p></section>";
    const races = t.left.length === 1 ? esc(R.lastRace) + raceName(model, c, t.left[0]) : esc(R.racesLeftList(t.left.length)) + t.left.map((ev) => raceName(model, c, ev)).join(", ");
    const rows3 = t.contenders.map(
      (x) => "<li>" + rankOrd(x.index + 1) + '<div class="wbox">' + who(x.name, "rname") + '<span class="subl">' + esc(condition(t, x)) + '</span></div><span class="tmax"><small>' + esc(R.max) + "</small>" + String(x.max) + '</span><span class="gapt">' + String(totalOf(k.riders[x.index])) + " " + T.pts + "</span></li>"
    ).join("");
    return '<section class="tpanel">' + head + '<span class="tleft">' + races + '</span></div><p class="tlead">' + rankOrd(1) + "<span>" + leadSentence(model, k, t) + "</span></p>" + (rows3 ? '<ol class="tlist">' + rows3 + "</ol>" : "") + '<p class="tfoot">' + (t.out ? esc(R.out(t.out)) : "") + esc(R.how(k.headers.includes(COLUMN_JOKER))) + "</p></section>";
  }
  function objective(c, k, rider) {
    const t = titleOf(c, k);
    const i = k.riders.findIndex((r) => r[COLUMN_NAME] === rider);
    if (!t || i < 0) return null;
    const R = T.titleRace;
    if (t.kind === "final") return i === 0 ? { tone: "champ", text: R.objChampion } : null;
    const race = t.left.length === 1 ? t.left[0] : null;
    if (i === 0) {
      if (t.clinched) return { tone: "sure", text: R.objSure(race) };
      if (race === null) return { tone: "lead", text: R.objLeads(t.left.length, t.contenders.length) };
      const n = t.leaderNeeds;
      if (n === "entry") return { tone: "lead", text: R.objEntry(race) };
      if (n === 1) return { tone: "lead", text: R.objWin(race) };
      return { tone: "lead", text: n === null ? R.objNotEnough(race) : R.objPlace(ordText(n), race) };
    }
    const x = t.contenders.find((y) => y.index === i);
    if (!x) return { tone: "out", text: R.objOut };
    return { tone: "open", text: race === null ? R.objContender(x.max, t.left.length) : condition(t, x) };
  }
  function objectiveLine(o) {
    return o ? '<p class="obj ' + o.tone + '">' + esc(o.text) + "</p>" : "";
  }

  // plugins/csm-standings/src/site/views/dashboard.ts
  function raceCard(model, e, cls, label2, extra = "") {
    return '<div class="race ' + cls + '"' + opens(e) + '><div class="rtop"><span class="k">' + label2 + "</span>" + schedIcon(e) + '</div><span class="n">' + esc(e.event) + '</span><span class="d">' + dateRange3(e) + atPlace(e, true) + "</span>" + extra + '<div class="chips">' + e.codes.map((code) => chip(model, code)).join("") + '</div><span class="go" aria-hidden="true">' + esc(T.event.link) + "</span></div>";
  }
  function dashboard(model, side, quit) {
    const { today, calendar } = model;
    const past = calendar.filter((e) => stateOf(e, today) === "past");
    const live = calendar.filter((e) => stateOf(e, today) === "live");
    const firstUpcoming = calendar.findIndex((e) => stateOf(e, today) === "upcoming");
    const next = firstUpcoming === -1 ? void 0 : calendar[firstUpcoming];
    const over = seasonPhase(model, seasonRaces(model).done) === "final";
    const last = past.length && !over ? past[past.length - 1] : void 0;
    const liveTag = '<span class="tag live">' + esc(T.live) + "</span>";
    const races = (next ? raceCard(
      model,
      next,
      "next",
      esc(T.nextRace),
      '<span class="count">' + String(daysUntil(next.start, today)) + "<small>" + esc(T.days) + "</small></span>"
    ) : "") + live.map((e) => raceCard(model, e, "", liveTag)).join("") + (last ? raceCard(model, last, "", esc(T.lastRace)) : "");
    return seasonBanner(model, side, quit) + (races ? '<div class="races">' + races + "</div>" : "") + calendarOf(model, calendar, T.calendarTitle(model.season), true) + sectionHead(T.leaders, T.leadersAside) + '<div class="leaders">' + model.champs.map((c) => leaderPanel(model, c)).join("") + "</div>" + leadersLegend(model);
  }
  var clinched = (c, k) => {
    const t = titleOf(c, k);
    return t?.kind === "open" && t.clinched;
  };
  function sureIcon(c, k) {
    if (!clinched(c, k)) return "<span></span>";
    const label2 = esc(T.leadersSure);
    return '<span class="lst" role="img" aria-label="' + label2 + '" title="' + label2 + '">✓</span>';
  }
  function leadersLegend(model) {
    return model.champs.some((c) => c.classes.some((k) => clinched(c, k))) ? '<p class="llegend"><span><i class="lst">✓</i>' + esc(T.leadersSure) + "</span></p>" : "";
  }
  function stakesLine(model, c) {
    const open = openRaces(c);
    if (!open.length) return "";
    const race = open[0].t.left[0];
    const at2 = atRace(c, race);
    const text = esc(T.leadersStakes(race, at2.open.length, at2.sure.length));
    const e = calFor(model, c.code, race);
    return e ? '<button type="button" class="lsum"' + eventAttrs(e) + ">" + text + '<span aria-hidden="true">›</span></button>' : '<span class="lsum">' + text + "</span>";
  }
  function sectionHead(title, aside, button = "") {
    const side = '<span class="aside">' + esc(aside) + "</span>";
    return '<div class="section-h"><h2>' + esc(title) + "</h2>" + (button ? '<span class="section-r">' + side + button + "</span>" : side) + "</div>";
  }
  var gapOf = (lead) => lead === 0 ? T.tieGap : "+" + String(lead);
  function leaderPanel(model, c) {
    const stakes = stakesLine(model, c);
    const blockedRow = (cls) => '<li><button type="button" class="lrow blocked" data-go="' + esc(c.code) + '" data-cls="' + esc(cls) + '"><span class="cl">' + esc(cls) + '</span><span class="nm">' + esc(T.updating) + "</span><span></span><span></span><span></span></button></li>";
    const rows3 = c.classes.length ? c.classes.map((k) => {
      if (k.blocked) return blockedRow(k.name);
      const [a, b] = k.riders;
      return '<li><button type="button" class="lrow" data-go="' + esc(c.code) + '" data-cls="' + esc(k.name) + '"><span class="cl">' + esc(k.name) + '</span><span class="nm">' + esc(a[COLUMN_NAME]) + '</span><span class="lpts">' + pts(totalOf(a)) + '</span><span class="gap">' + (k.riders.length > 1 ? gapOf(totalOf(a) - totalOf(b)) : "") + "</span>" + sureIcon(c, k) + "</button></li>";
    }).join("") : blockedRow("");
    return '<section class="lpanel' + (stakes ? " open" : "") + '" style="--c:' + esc(c.color) + '"><header><button type="button" class="lgo" data-sel="' + esc(c.code) + '" aria-label="' + esc(T.standingsTitle + T.colon + c.name) + '"></button>' + logo(c) + '<div><div class="t">' + esc(c.name) + ' <span class="lgo-i" aria-hidden="true">›</span></div><div class="s">' + champStatus(c) + "</div>" + stakes + "</div></header><ol>" + rows3 + "</ol></section>";
  }

  // plugins/csm-standings/src/site/views/event-page.ts
  var SHOWN = 5;
  var INTEGER = /^\d+$/;
  function champsOf(model, e) {
    return e.codes.map((code) => model.byCode.get(code)).filter((c) => c.events.includes(e.event));
  }
  function outcome(p) {
    const vs3 = p.values.map(([, v]) => v);
    if (vs3.some((v) => INTEGER.test(v) && v !== "0")) return 0;
    if (vs3.includes("0")) return 1;
    if (vs3.includes("DNF")) return 2;
    return vs3.includes("DNS") ? 3 : 4;
  }
  function mergedOrder(lists) {
    const next = /* @__PURE__ */ new Map();
    const before = /* @__PURE__ */ new Map();
    const rank = /* @__PURE__ */ new Map();
    for (const list3 of lists)
      list3.forEach((n, i) => {
        if (!next.has(n)) next.set(n, /* @__PURE__ */ new Set());
        before.set(n, before.get(n) ?? 0);
        rank.set(n, Math.min(rank.get(n) ?? 1, (i + 1) / (list3.length + 1)));
      });
    for (const list3 of lists)
      for (let i = 1; i < list3.length; i++) {
        const after = next.get(list3[i - 1]);
        if (!after.has(list3[i])) {
          after.add(list3[i]);
          before.set(list3[i], before.get(list3[i]) + 1);
        }
      }
    const out = [];
    const ready = [...before].filter(([, n]) => n === 0).map(([name2]) => name2);
    while (ready.length) {
      ready.sort((a, b) => rank.get(a) - rank.get(b));
      const name2 = ready.shift();
      out.push(name2);
      for (const m of next.get(name2)) {
        before.set(m, before.get(m) - 1);
        if (before.get(m) === 0) ready.push(m);
      }
    }
    return out;
  }
  var isOut = (v) => v === "DNF" || v === "DNS";
  function tags(model, values) {
    const v = values[0][1];
    return '<span class="evtags">' + values.map(
      ([code]) => '<i class="cbar" style="--c:' + esc(model.byCode.get(code).color) + '" title="' + esc(code) + '"></i>'
    ).join("") + '<span class="rpts' + (isOut(v) ? " sp" : "") + '">' + esc(v) + "</span></span>";
  }
  function classResult(model, parts, ev, shared, hit) {
    const name2 = parts[0].k.name;
    const head = "<header><h3>" + esc(name2) + "</h3>";
    const card3 = (body) => '<article class="rcard' + (hit ? " hit" : "") + '">' + head + body + "</article>";
    if (parts.some((p) => p.k.blocked)) return { html: card3("</header>" + notice()), name: name2, winner: null, starters: [] };
    const people = /* @__PURE__ */ new Map();
    for (const { c, k } of parts)
      for (const r of k.riders) {
        const n = r[COLUMN_NAME];
        let p = people.get(n);
        if (!p) people.set(n, p = { name: n, plate: r[COLUMN_PLATE], values: [] });
        p.values.push([c.code, r[ev]]);
      }
    const all = [...people.values()];
    if (all.every((p) => p.values.every(([, v]) => v === "" || v === "N/A"))) return null;
    const cancelled = parts.find(
      ({ c, k }) => isCancelledRace(
        k.riders.map((r) => [resultOf(r[ev])]),
        0,
        c.scale
      )
    );
    if (cancelled) {
      const top2 = String(cancelled.c.scale[0]);
      const present = all.filter((p) => p.values.some(([, v]) => v === top2)).map((p) => p.name);
      return {
        html: card3('</header><p class="rnote">' + esc(T.races.cancelled(cancelled.c.scale[0], present.length)) + "</p>"),
        name: name2,
        winner: null,
        starters: present
      };
    }
    const order = mergedOrder(
      parts.map(
        ({ c, k }) => k.riders.filter((r) => INTEGER.test(r[ev]) && r[ev] !== "0").map((r) => ({ name: r[COLUMN_NAME], p: c.scale.indexOf(Number(r[ev])) })).sort((a, b) => a.p - b.p).map((x) => x.name)
      )
    );
    const lines = [
      ...order.map((n) => people.get(n)),
      ...all.filter((p) => !order.includes(p.name) && outcome(p) < 4).sort((a, b) => outcome(a) - outcome(b))
    ];
    const line = (p, i) => {
      const placed = i < order.length;
      const entered = p.values.filter(([, v]) => v !== "X" && v !== "" && v !== "N/A");
      return '<li class="' + (placed && i < 3 ? "p" + String(i + 1) : "") + '"><span class="rp">' + (placed ? String(i + 1) : T.noValue) + "</span>" + who(p.name, "rname") + '<span class="rno">' + esc(p.plate) + "</span>" + (shared ? tags(model, entered) : '<span class="rpts' + (isOut(entered[0][1]) ? " sp" : "") + '">' + esc(entered[0][1]) + "</span>") + "</li>";
    };
    const list3 = shared ? "rlist multi" : "rlist";
    const started = lines.filter((p) => outcome(p) <= 2);
    const notStarted = lines.length - started.length;
    const absent = all.length - lines.length;
    const more = lines.slice(SHOWN);
    return {
      name: name2,
      winner: order.length ? order[0] : null,
      starters: started.map((p) => p.name),
      html: card3(
        "<span>" + esc(T.races.started(started.length)) + '</span></header><ol class="' + list3 + '">' + lines.slice(0, SHOWN).map(line).join("") + "</ol>" + (more.length ? '<details class="rmore"><summary>' + esc(T.races.allResults(lines.length)) + '</summary><ol class="' + list3 + '">' + more.map((p, i) => line(p, i + SHOWN)).join("") + "</ol></details>" : "") + (notStarted ? '<p class="rabs">' + esc(T.races.notStarted(notStarted)) + "</p>" : "") + (absent ? '<p class="rabs">' + esc(T.races.notRaced(absent)) + "</p>" : "")
      )
    };
  }
  function titleAfter(c, k, count2) {
    const championship2 = championshipOf(c.rulebook, c.code);
    const riders = k.riders.map((r) => ({
      name: r[COLUMN_NAME],
      results: c.events.map((ev, i) => i < count2 || r[ev] === "N/A" ? resultOf(r[ev]) : "")
    }));
    const { order } = rankOnEvents(k.name, riders, c.events, c.events.length, championship2);
    const t = titleRace(
      k.name,
      order.map((i) => riders[i]),
      c.events,
      championship2
    );
    const race = t;
    if (race.kind === "final") return race.champion;
    return race.clinched ? race.leader : null;
  }
  function titlesDecided(c, ev) {
    const i = c.events.indexOf(ev);
    return c.classes.flatMap((k) => {
      if (k.blocked || !k.riders.some((r) => r[ev] !== "" && r[ev] !== "N/A")) return [];
      const name2 = titleAfter(c, k, i + 1);
      return name2 !== null && titleAfter(c, k, i) === null ? [{ k, name: name2 }] : [];
    });
  }
  function hero(model, e, run) {
    const st = stateOf(e, model.today);
    const label2 = run ? T.races.results : st === "live" ? T.live : st === "upcoming" ? T.nextRace : T.races.resultsToCome;
    return '<button type="button" class="back" data-back="1">' + esc(T.back) + '</button><div class="ehero"><div><p class="eyebrow">' + esc(T.event.eyebrow(model.season)) + ' · <span class="ek' + (st === "live" && !run ? " live" : "") + '">' + esc(label2) + "</span></p><h2>" + esc(e.event) + '</h2><p class="ed">' + (run ? raceDaysText(e) : dateRange3(e)) + schedIcon(e) + "</p>" + venueBlock(e) + '</div><div class="echips">' + e.codes.map((code) => {
      const c = model.byCode.get(code);
      return '<button type="button" class="chip" data-sel="' + esc(code) + '" style="--c:' + esc(c.color) + '" title="' + esc(c.name) + '">' + esc(code) + "</button>";
    }).join("") + "</div></div>";
  }
  function links(champs) {
    const E = T.event;
    return sectionHead(E.byChamp, E.byChampAside) + '<div class="elinks">' + champs.map(
      (c) => '<div class="elink" style="--c:' + esc(c.color) + '">' + logo(c) + "<div><b>" + esc(c.name) + '</b><span><button type="button" class="evlink" data-sel="' + esc(c.code) + '">' + esc(E.champStandings(c.code)) + "</button></span></div></div>"
    ).join("") + "</div>";
  }
  var groupHead = (c, text) => '<h3><span class="chip" style="--c:' + esc(c.color) + '">' + esc(c.code) + "</span>" + esc(text) + "</h3>";
  function toCome(model, e, champs) {
    const E = T.event;
    const R = T.titleRace;
    const here = champs.map((c) => ({ c, ...atRace(c, e.event) }));
    const groups = (pick, count2) => here.filter((h) => h[pick].length).map(
      (h) => '<div class="egroup">' + groupHead(h.c, count2(h[pick].length)) + '<div class="stakes">' + h[pick].map(({ k, t }) => stakeTile(k, t, 'data-go="' + esc(h.c.code) + '" data-cls="' + esc(k.name) + '"')).join("") + "</div></div>"
    ).join("");
    const sure = groups("sure", R.sureAside);
    const open = groups("open", E.stakesCount);
    const upcoming = stateOf(e, model.today) === "upcoming";
    return '<p class="tnote">' + esc((upcoming ? T.races.inDays(daysUntil(e.start, model.today)) : "") + T.races.later) + "</p>" + (sure ? sectionHead(
      R.sureTitle,
      here.filter((h) => h.sure.length).map((h) => h.c.code).join(" · ")
    ) + sure : "") + sectionHead(R.stakesTitle(e.event), champs.map((c) => c.code).join(" · ")) + (open || '<p class="tnote">' + esc(E.noStakes) + "</p>");
  }
  function results(model, e, champs, cls) {
    const E = T.event;
    const shared = champs.length > 1;
    const byOrder3 = model.champs.filter((c) => champs.includes(c));
    const names = [...new Set(byOrder3.flatMap((c) => c.classes.map((k) => k.name)))];
    const classes = names.flatMap((n) => {
      const parts = champs.flatMap((c) => c.classes.filter((k) => k.name === n).map((k) => ({ c, k })));
      const r = classResult(model, parts, e.event, shared, n === cls);
      return r ? [r] : [];
    });
    const titles = champs.map((c) => ({ c, won: titlesDecided(c, e.event) }));
    const decided = titles.reduce((n, t) => n + t.won.length, 0);
    const winners = classes.filter((r) => r.winner !== null);
    const stat = (n, label2) => "<div><b>" + String(n) + "</b><span>" + esc(label2) + "</span></div>";
    return '<div class="pstats">' + stat(new Set(classes.flatMap((r) => r.starters)).size, E.statStarters) + stat(classes.length, E.statClasses) + stat(champs.length, E.statChamps) + stat(decided, E.statTitles) + "</div>" + (decided ? sectionHead(E.titlesHere(e.event), E.titlesAside) + titles.filter((t) => t.won.length).map(
      ({ c, won }) => '<div class="egroup">' + groupHead(c, E.titlesCount(won.length, c.events[c.events.length - 1] === e.event)) + '<ul class="ewins etit">' + won.map(
        (d) => '<li><button type="button" class="cl" data-go="' + esc(c.code) + '" data-cls="' + esc(d.k.name) + '">' + esc(d.k.name) + '</button><span class="star" aria-hidden="true">★</span>' + who(d.name, "rname") + "</li>"
      ).join("") + "</ul></div>"
    ).join("") : "") + (winners.length ? sectionHead(E.winners, T.categories(winners.length)) + '<ul class="ewins">' + winners.map(
      (r) => '<li><span class="cl">' + esc(r.name) + '</span><span class="rp">1</span>' + who(r.winner, "rname") + "</li>"
    ).join("") + "</ul>" : "") + sectionHead(E.results, E.resultsAside) + (shared ? '<p class="tnote">' + esc(E.shared(champs.map((c) => c.code).join(" + "))) + "</p>" : "") + '<div class="rcards">' + classes.map((r) => r.html).join("") + "</div>";
  }
  function eventPage(model, e, cls) {
    const champs = champsOf(model, e);
    const run = champs.some((c) => hasResults(c, e.event));
    return hero(model, e, run) + (run ? results(model, e, champs, cls) : toCome(model, e, champs)) + links(champs);
  }

  // plugins/csm-standings/src/site/views/season-grid.ts
  function raceResult(model, e, ev) {
    const v = e.row[ev];
    if (v === "") return { kind: "future" };
    if (v === "N/A") return { kind: "na" };
    const result = resultOf(v);
    if (typeof result === "number") {
      const scale = model.byCode.get(e.code).scale;
      const column = e.klass.riders.map((r) => [resultOf(r[ev])]);
      if (isCancelledRace(column, 0, scale))
        return { kind: "pts", text: "<b>" + esc(v) + "</b> " + T.notRun, place: null };
      const p = scale.indexOf(result) + 1;
      return {
        kind: "pts",
        text: "<b>" + (p ? ord(p) : T.outOfPoints) + "</b> · " + esc(v) + " " + T.ptsOf(result),
        place: p || 99
      };
    }
    if (v === "X") return { kind: "x" };
    return { kind: "bad", text: '<span class="bad">' + esc(v) + "</span>" };
  }
  function riderRaces(model, all) {
    return model.calendar.filter(
      (ev) => all.some((e) => ev.codes.includes(e.code) && model.byCode.get(e.code).events.includes(ev.event))
    );
  }
  function gridCells(model, all) {
    const events = riderRaces(model, all);
    const rows3 = [];
    for (const [, list3] of groupsOf(model, all, null)) {
      for (const e of list3) {
        let row2 = rows3.find((r) => r.cls === e.cls);
        if (!row2) rows3.push(row2 = { cls: e.cls, entries: [] });
        row2.entries.push(e);
      }
    }
    const cells = rows3.map(
      (row2) => events.map((ev) => ({
        ev,
        cls: row2.cls,
        parts: row2.entries.filter((e) => ev.codes.includes(e.code) && model.byCode.get(e.code).events.includes(ev.event)).flatMap((e) => {
          const r = raceResult(model, e, ev.event);
          return r.kind === "na" ? [] : [{ e, r, jk: e.row[COLUMN_JOKER] === ev.event }];
        })
      }))
    );
    return { rows: rows3, cells };
  }
  function shortEvent(ev) {
    const [a, ...rest] = ev.split(/[\s-]+/);
    return (a.length > 5 ? a.slice(0, 4) : a) + (rest.length ? " " + rest.join(" ") : "");
  }
  function shown(c) {
    return c.parts.find((q) => q.r.kind !== "future") ?? c.parts[0];
  }
  function cellHtml2(c, i, j) {
    if (!c.parts.length) return '<td class="off"></td>';
    const p = shown(c);
    const value = (q) => q.e.row[c.ev.event];
    const jk = c.parts.some((q) => q.jk);
    const r = p.r;
    let inner;
    let cls;
    if (r.kind === "future") [inner, cls] = ["", "fut"];
    else if (r.kind === "x") [inner, cls] = ["–", "abs"];
    else if (r.kind === "bad") [inner, cls] = [esc(value(p)), "bad"];
    else if (r.place === null) [inner, cls] = [esc(value(p)), "nr"];
    else if (r.place === 99) [inner, cls] = ["0", "out"];
    else [inner, cls] = [String(r.place), r.place <= 3 ? "p" + String(r.place) : "pts"];
    return '<td><button type="button" class="gc ' + cls + (jk ? " jk" : "") + '" data-cell="' + String(i) + "-" + String(j) + '" aria-label="' + esc(c.ev.event) + ", " + esc(c.cls) + '">' + inner + "</button></td>";
  }
  function codeBars(model, codes) {
    return '<span class="gcodes">' + codes.map(
      (code) => '<i style="--c:' + esc(model.byCode.get(code).color) + '" title="' + esc(code) + '"></i>'
    ).join("") + "</span>";
  }
  var GRID_LEGEND = "legend";
  function legend(model, all) {
    return legendDialog(GRID_LEGEND, T.legendTitle, T.legendIntro + " " + T.gridHint, [
      ['<span class="gc p1">1</span><span class="gc p2">2</span><span class="gc p3">3</span>', T.legendPodium],
      ['<span class="gc pts">7</span>', T.legendPlace],
      ['<span class="gc out">0</span>', T.legendOut],
      ['<span class="gc nr">25</span>', T.legendNotRun],
      ['<span class="gc bad">DNF</span>', T.legendDnf],
      ['<span class="gc abs">–</span>', T.legendAbsent],
      ['<span class="gc fut"></span>', T.legendFuture],
      ['<span class="gc jk pts">9</span>', T.legendJoker],
      [codeBars(model, [...new Set(all.map((e) => e.code))]), T.legendCodes]
    ]);
  }
  function seasonGrid(model, all) {
    const { rows: rows3, cells } = gridCells(model, all);
    const events = riderRaces(model, all);
    const html = '<div class="gbox"><div class="gscroll"><table class="grid"><thead><tr><th class="gcls" scope="col"><span class="sr">' + esc(T.classLabel) + "</span></th>" + events.map(
      (ev) => '<th scope="col" title="' + esc(ev.event + (ev.venue ? " · " + ev.venue : "")) + '"><span class="ge">' + esc(shortEvent(ev.event)) + '</span><span class="gd">' + raceDaysText(ev) + "</span></th>"
    ).join("") + "</tr></thead><tbody>" + rows3.map(
      (row2, i) => '<tr><th class="gcls" scope="row">' + esc(row2.cls) + codeBars(model, [...new Set(row2.entries.map((e) => e.code))]) + "</th>" + cells[i].map((c, j) => cellHtml2(c, i, j)).join("") + "</tr>"
    ).join("") + '</tbody></table></div><p class="gdetail" aria-live="polite">' + esc(T.gridHint) + "</p>" + legend(model, all) + "</div>";
    return { html, cells };
  }
  function gridDetail(model, c) {
    const { r } = shown(c);
    const body = r.kind === "future" ? T.toCome : r.kind === "x" ? T.absent : r.text;
    const tag = (p) => '<span class="cc" style="--c:' + esc(model.byCode.get(p.e.code).color) + '">' + esc(p.e.code) + (p.jk ? "<em>" + T.joker + "</em>" : "") + "</span>";
    const what = '<span class="gv">' + body + "</span> " + c.parts.map(tag).join("");
    return "<b>" + esc(c.ev.event) + "</b> · " + raceDaysText(c.ev) + " · " + esc(c.cls) + T.colon + what + " " + schedIcon(c.ev) + " " + eventLink(c.ev, T.event.link);
  }

  // plugins/csm-standings/src/site/palmares-data.ts
  var NUMBER4 = /^\d+$/;
  var cut = (items, max = 5) => items.slice(0, max).join(" · ") + (items.length > max ? T.gala.and(items.length - max) : "");
  var nameOf = (r) => r[COLUMN_NAME];
  var cellOf = (r, ev) => r[ev];
  function finalsOf(model) {
    const out = [];
    for (const e of model.calendar) {
      const champs = champsOf(model, e).filter((c) => !c.blocked);
      const names = [...new Set(champs.flatMap((c) => c.classes.map((k) => k.name)))];
      for (const cls of names) {
        const parts = champs.flatMap((c) => c.classes.filter((k) => k.name === cls).map((k) => ({ c, k })));
        if (parts.some((p) => p.k.blocked)) continue;
        const values = parts.flatMap(({ k }) => k.riders.map((r) => [nameOf(r), cellOf(r, e.event)]));
        if (values.every(([, v]) => v === "" || v === "N/A")) continue;
        const notRun = parts.some(
          ({ c, k }) => isCancelledRace(
            k.riders.map((r) => [resultOf(cellOf(r, e.event))]),
            0,
            c.scale
          )
        );
        const order = notRun ? [] : mergedOrder(
          parts.map(
            ({ c, k }) => k.riders.filter((r) => NUMBER4.test(cellOf(r, e.event)) && cellOf(r, e.event) !== "0").map((r) => ({ n: nameOf(r), p: c.scale.indexOf(Number(cellOf(r, e.event))) })).sort((a, b) => a.p - b.p).map((x) => x.n)
          )
        );
        out.push({
          e,
          cls,
          codes: parts.map((p) => p.c.code),
          notRun,
          order,
          tookPart: new Set(values.filter(([, v]) => NUMBER4.test(v) || v === "DNF" || v === "DNS").map(([n]) => n)),
          started: new Set(values.filter(([, v]) => NUMBER4.test(v) || v === "DNF").map(([n]) => n)),
          listed: new Set(values.map(([n]) => n))
        });
      }
    }
    return out;
  }
  function entryOf(model, c, k, r) {
    return model.people.get(nameOf(r)).find((e) => e.code === c.code && e.cls === k.name);
  }
  function pathOf(model, c, k, r) {
    const e = entryOf(model, c, k, r);
    return c.events.flatMap((ev) => {
      const res = raceResult(model, e, ev);
      if (res.kind === "na") return [];
      const v = cellOf(r, ev);
      const race = calFor(model, c.code, ev) ?? null;
      const cell = (kind, text) => [{ ev, short: shortEvent(ev), kind, text, race }];
      if (res.kind === "future") return cell("fut", "");
      if (res.kind === "x") return cell("abs", "–");
      if (res.kind === "bad") return cell("bad", v);
      if (res.place === null) return cell("nr", v);
      if (res.place === 99) return cell("out", "0");
      return cell(res.place <= 3 ? "p" + String(res.place) : "pts", String(res.place));
    });
  }
  function classResult2(model, c, k) {
    const held = (ev) => k.riders.some((r) => cellOf(r, ev) !== "" && cellOf(r, ev) !== "N/A");
    const done = c.events.filter(held).length;
    const planned = c.events.filter((ev) => !k.riders.every((r) => cellOf(r, ev) === "N/A")).length;
    const rs = k.riders;
    return {
      champ: c,
      k,
      blocked: k.blocked,
      final: !k.blocked && done === planned,
      done,
      planned,
      riders: rs.length,
      podium: k.blocked ? [] : rs.slice(0, 3).map((r, i) => ({ name: nameOf(r), no: r[COLUMN_PLATE], pts: totalOf(r), rank: i + 1 })),
      lead: k.blocked || rs.length < 2 ? null : totalOf(rs[0]) - totalOf(rs[1]),
      // A class that isn't blocked has a rider (a row makes the class).
      path: k.blocked ? [] : pathOf(model, c, k, rs[0])
    };
  }
  function best(items, value, name2) {
    const top2 = Math.max(0, ...items.map(value));
    if (!top2) return null;
    return { value: top2, who: items.filter((x) => value(x) === top2).map(name2) };
  }
  var label = (r) => r.champ.code + " " + r.k.name;
  function tiebreakWhy(r) {
    const W = T.gala.why;
    const { champ: c, k } = r;
    const t = tiebreakOfClass(c, k);
    if (t.kind === "place") return t.place === 1 ? W.wins(t.a, t.b) : W.places(t.place, t.a, t.b);
    if (t.kind === "outside") return W.outside(t.a, t.b);
    if (t.kind === "name") return W.name;
    if (!t.last) return W.earlier(c.events[t.event]);
    const ev = c.events[t.event];
    const show = (row2) => {
      const v = resultOf(cellOf(row2, ev));
      return typeof v === "number" ? W.points(v) : v === "X" ? W.absent : v;
    };
    return W.last(ev, show(k.riders[0]), show(k.riders[1]));
  }
  function palmaresOf(model) {
    const G = T.gala;
    const finals = finalsOf(model);
    const champs = model.champs.filter((c2) => !c2.blocked).map((c2) => ({
      champ: c2,
      final: c2.done === c2.total,
      classes: c2.classes.map((k) => classResult2(model, c2, k)),
      riders: c2.riders
    }));
    const classes = champs.flatMap((c2) => c2.classes).filter((r) => !r.blocked && r.riders > 0);
    const finished = classes.filter((r) => r.final);
    const racesRun = seasonRaces(model).done;
    const riders = new Set(finals.flatMap((f) => [...f.tookPart]));
    const figures = [
      { label: G.figures.championships, value: champs.length },
      { label: G.figures.categories, value: new Set(classes.map((r) => r.k.name)).size },
      { label: G.figures.riders, value: riders.size },
      { label: G.figures.races, value: racesRun, of: model.calendar.length },
      { label: G.figures.finals, value: finals.filter((f) => !f.notRun && f.started.size > 0).length }
    ];
    const facts = [];
    const titles = /* @__PURE__ */ new Map();
    for (const r of finished) titles.set(r.podium[0].name, [...titles.get(r.podium[0].name) ?? [], r]);
    const t = best(
      [...titles],
      ([, l]) => l.length,
      ([n]) => n
    );
    if (t && t.value > 1) {
      const list3 = titles.get(t.who[0]);
      facts.push({
        code: "TITLES",
        title: G.titles,
        value: String(t.value),
        unit: G.titlesUnit,
        who: t.who,
        detail: t.who.length === 1 ? G.inChamps(new Set(list3.map((r) => r.champ.code)).size) + T.colon + cut(list3.map(label)) + "." : ""
      });
    }
    const wins = /* @__PURE__ */ new Map();
    const podiums = /* @__PURE__ */ new Map();
    for (const f of finals)
      f.order.slice(0, 3).forEach((n, i) => {
        podiums.set(n, (podiums.get(n) ?? 0) + 1);
        if (i === 0) wins.set(n, (wins.get(n) ?? 0) + 1);
      });
    const w = best(
      [...wins],
      ([, v]) => v,
      ([n]) => n
    );
    if (w)
      facts.push({
        code: "WINS",
        title: G.wins,
        value: String(w.value),
        unit: G.winsUnit,
        who: w.who,
        detail: G.winsDetail
      });
    const p = best(
      [...podiums],
      ([, v]) => v,
      ([n]) => n
    );
    if (p)
      facts.push({
        code: "PODIUMS",
        title: G.podiums,
        value: String(p.value),
        unit: G.podiumsUnit,
        who: p.who,
        detail: G.podiumsDetail
      });
    const perfect = finished.filter((r) => {
      if (r.riders < 2) return false;
      const fs = finals.filter((f) => f.cls === r.k.name && f.codes.includes(r.champ.code) && !f.notRun);
      return fs.length >= 2 && fs.every((f) => f.order[0] === r.podium[0].name);
    });
    if (perfect.length) {
      const byRider = /* @__PURE__ */ new Map();
      for (const r of perfect) byRider.set(r.podium[0].name, [...byRider.get(r.podium[0].name) ?? [], r]);
      const list3 = [...byRider].sort(([a, x], [b, y]) => y.length - x.length || a.localeCompare(b, "fr")).map(([name2, rs]) => ({ name: name2, note: rs.map(label).join(" · ") }));
      facts.push({
        code: "PERFECT",
        title: G.perfect,
        value: String(perfect.length),
        unit: G.perfectUnit(perfect.length),
        who: list3.map((x) => x.name),
        detail: G.perfectDetail,
        list: list3
      });
    }
    const duels = finished.filter((r) => r.lead !== null);
    if (duels.length) {
      const leads = duels.map((r) => r.lead);
      const min = Math.min(...leads);
      const tight = duels.filter((r) => r.lead === min);
      facts.push({
        code: "TIGHT",
        title: G.tight,
        value: String(min),
        unit: G.tightUnit(min),
        who: tight.map((r) => r.podium[0].name),
        detail: min === 0 ? G.tightTie : G.tightGap,
        duels: tight.map((r) => ({
          cls: label(r),
          code: r.champ.code,
          k: r.k.name,
          a: r.podium[0].name,
          ap: r.podium[0].pts,
          b: r.podium[1].name,
          bp: r.podium[1].pts,
          why: min === 0 ? tiebreakWhy(r) : ""
        }))
      });
      const max = Math.max(...leads);
      const wide = duels.filter((r) => r.lead === max);
      facts.push({
        code: "WIDE",
        title: G.wide,
        value: String(max),
        unit: G.wideUnit,
        who: wide.map((r) => r.podium[0].name),
        detail: cut(wide.map(label)) + "."
      });
    }
    const expected = /* @__PURE__ */ new Map();
    const present = /* @__PURE__ */ new Map();
    for (const f of finals)
      for (const n of f.listed) {
        expected.set(n, (expected.get(n) ?? 0) + 1);
        if (f.tookPart.has(n)) present.set(n, (present.get(n) ?? 0) + 1);
      }
    const iron = [...expected].filter(([n, e]) => e >= 3 && present.get(n) === e);
    if (iron.length) {
      const list3 = iron.sort(([a, x], [b, y]) => y - x || a.localeCompare(b, "fr")).map(([name2, e]) => ({ name: name2, note: G.finals(e) }));
      facts.push({
        code: "IRON",
        title: G.iron,
        value: String(iron.length),
        unit: G.ironUnit,
        who: list3.map((x) => x.name),
        detail: G.ironDetail,
        list: list3
      });
    }
    const people = [...model.people].map(([n, all]) => ({
      n,
      cls: new Set(all.map((e) => e.cls)).size,
      champs: new Set(all.map((e) => e.code)).size,
      entries: all.length
    }));
    const c = best(
      people,
      (x) => x.cls,
      (x) => x.n
    );
    if (c && c.value > 1) {
      const one = people.find((x) => x.n === c.who[0]);
      facts.push({
        code: "CLASSES",
        title: G.classes,
        value: String(c.value),
        unit: G.classesUnit,
        who: c.who,
        detail: c.who.length === 1 ? G.classesDetail(one.champs, one.entries) : ""
      });
    }
    const field2 = best(
      finals,
      (f) => f.started.size,
      (f) => f.cls + " · " + f.e.event
    );
    if (field2)
      facts.push({
        code: "FIELD",
        title: G.field,
        value: String(field2.value),
        unit: G.fieldUnit,
        who: field2.who,
        detail: G.fieldDetail
      });
    const final = champs.every((x) => x.final);
    return { season: model.season, champs, final, figures, facts: lineupOf(model.highlights, facts, final) };
  }
  function factTitle(code) {
    const G = T.gala;
    const titles = {
      TITLES: G.titles,
      WINS: G.wins,
      PODIUMS: G.podiums,
      PERFECT: G.perfect,
      TIGHT: G.tight,
      WIDE: G.wide,
      IRON: G.iron,
      CLASSES: G.classes,
      FIELD: G.field
    };
    return titles[code];
  }
  function manualTexts(m) {
    return m.texts[siteLanguages().lang] ?? Object.values(m.texts)[0];
  }
  function lineupOf(lineup, computed, final) {
    if (lineup === null) return [];
    return lineup.filter((r) => r.show).map((r) => ({ ...factOf(r, computed, final), ...r.trophy ? { trophy: true } : {} }));
  }
  function factOf(r, computed, final) {
    const G = T.gala;
    if (r.manual) {
      const { value, names } = r.manual;
      const t = manualTexts(r.manual);
      return {
        code: r.code,
        title: t.TITLE,
        value,
        unit: t.UNIT,
        who: names,
        detail: t.DETAIL,
        manual: true,
        explain: { text: t.EXPLANATION, example: "" }
      };
    }
    const code = r.code;
    const f = computed.find((x) => x.code === code);
    if (f) return { ...f, explain: G.explain[code] };
    const c = G.challenges[code];
    return {
      code,
      title: factTitle(code),
      value: "",
      unit: "",
      who: [],
      detail: "",
      challenge: { goal: c.goal, unit: c.unit, text: final ? c.over : c.progress, next: final },
      explain: G.explain[code]
    };
  }
  function byList(items, name2, list3) {
    if (list3 === null) return [...items];
    return inListOrder(
      items.map((t) => ({ name: name2(t), t })),
      list3
    ).items.map((x) => x.t);
  }
  function galaOrdered(model, p) {
    const { champs, classes } = model.gala;
    return {
      model: { ...model, champs: byList(model.champs, (c) => c.code, champs) },
      palmares: {
        ...p,
        champs: byList(p.champs, (c) => c.champ.code, champs).map((c) => ({
          ...c,
          classes: byList(c.classes, (k) => k.k.name, classes)
        }))
      }
    };
  }

  // plugins/csm-standings/src/site/views/promotions.ts
  var levelName = (level) => T.gala.show.promotions.levels[level] ?? level;
  var nextSeason = (season) => String(Number(season) + 1);
  function disciplinesHtml(list3) {
    const by = /* @__PURE__ */ new Map();
    for (const p of list3) {
      const passages = by.get(p.discipline) ?? /* @__PURE__ */ new Map();
      const passage = levelName(p.from) + " → " + levelName(p.to);
      passages.set(passage, (passages.get(passage) ?? 0) + 1);
      by.set(p.discipline, passages);
    }
    return '<div class="pr-discs">' + [...by].map(
      ([discipline, passages], i) => '<div class="pr-disc" style="--i:' + String(i) + '"><h3>' + esc(discipline) + "</h3>" + [...passages].map(([passage, n]) => "<p><span>" + esc(passage) + "</span><b>" + String(n) + "</b></p>").join("") + "</div>"
    ).join("") + "</div>";
  }
  var plateHeight = (n) => Math.min(76, Math.floor((400 - (Math.ceil(n / 2) - 1) * 10) / Math.ceil(n / 2)));
  function platesHtml(plates) {
    const n = plates.length;
    return '<span class="pr-plates n' + String(Math.min(n, 4)) + '"' + (n >= 4 ? ' style="--h:' + String(plateHeight(n)) + 'px"' : "") + ">" + plates.map((p) => '<span class="pr-plate">' + esc(p) + "</span>").join("") + "</span>";
  }
  function nameHtml(name2) {
    const cut2 = name2.indexOf(" ");
    const first = cut2 < 0 ? "" : name2.slice(0, cut2);
    const last = cut2 < 0 ? name2 : name2.slice(cut2 + 1);
    return (first ? '<span class="pr-first">' + esc(first) + "</span>" : "") + // Its size from its length: the longest family name still fits the card's width (CSS, container units).
    '<span class="pr-last" style="--n:' + String(Math.max(last.length, 8)) + '">' + esc(last) + "</span>";
  }
  function promotionHtml(p, season, shown2, auto, delay) {
    const P = T.gala.show.promotions;
    const waiting = shown2 === 0;
    return '<div class="sl sl-promo ' + (waiting ? auto ? "auto won" : "hold" : "won whole") + '"' + (waiting && auto ? ' style="--d:' + delay + '"' : "") + '><div class="pr-card"><svg class="pr-guil" viewBox="0 0 200 200" aria-hidden="true">' + Array.from(
      { length: 24 },
      (_, i) => '<ellipse cx="100" cy="100" rx="96" ry="38" transform="rotate(' + String(i * 7.5) + ' 100 100)"/>'
    ).join("") + '</svg><span class="pr-sheen" aria-hidden="true"></span><div class="pr-head"><span class="pr-badge">' + esc(p.discipline) + '</span><span class="pr-lic">' + esc(P.licence(nextSeason(season))) + '</span></div><div class="pr-body"><div class="pr-pl"><span class="pr-lab">' + esc(P.plate(p.plates.length)) + '</span><div class="pr-in pr-in1">' + platesHtml(p.plates) + '</div></div><div class="pr-lv"><span class="pr-lab">' + esc(P.level) + '</span><div class="pr-levels"><span class="pr-old">' + esc(levelName(p.from)) + '</span><span class="pr-arrow" aria-hidden="true">→</span><span class="pr-stamp">' + esc(levelName(p.to)) + '</span></div></div><div class="pr-name"><span class="pr-lab">' + esc(P.rider) + '</span><span class="pr-blank" aria-hidden="true"></span><div class="pr-in pr-in2">' + nameHtml(p.name) + "</div></div></div></div></div>";
  }
  function revealPromotion(slide2) {
    slide2.classList.replace("hold", "won");
  }
  function fitPromotionNames(root, win) {
    const names = [...root.querySelectorAll(".pr-last")];
    if (!names.length) return;
    const fit2 = () => {
      for (const el of names) {
        el.style.fontSize = "";
        const room = el.closest(".pr-name").clientWidth;
        if (el.scrollWidth > room)
          el.style.fontSize = String(parseFloat(win.getComputedStyle(el).fontSize) * room / el.scrollWidth) + "px";
      }
    };
    fit2();
    const fonts = root.ownerDocument.fonts;
    if (fonts && fonts.status !== "loaded") void fonts.ready.then(fit2);
  }

  // plugins/csm-standings/src/site/views/trophy.ts
  function trophySvg(id, muted = false) {
    const stops2 = muted ? '<stop offset="0" stop-color="#f1f2f4"/><stop offset=".55" stop-color="#b9bec6"/><stop offset="1" stop-color="#7d838d"/>' : '<stop offset="0" stop-color="#fff6cf"/><stop offset=".35" stop-color="#f3d77a"/><stop offset=".75" stop-color="#B8952E"/><stop offset="1" stop-color="#7a5f17"/>';
    const fill = "url(#" + id + ")";
    return '<svg class="tr-svg' + (muted ? " muted" : "") + '" viewBox="0 0 64 72" aria-hidden="true" focusable="false"><defs><linearGradient id="' + id + '" x1="0" y1="0" x2="0" y2="72" gradientUnits="userSpaceOnUse">' + stops2 + '</linearGradient></defs><g fill="none" stroke="' + fill + '" stroke-width="4.5" stroke-linecap="round"><path d="M17 12H10.5C8 12 6.5 13.6 6.8 16.2 7.6 23 11.5 28.6 18.5 30.5"/><path d="M47 12h6.5c2.5 0 4 1.6 3.7 4.2-.8 6.8-4.7 12.4-11.7 14.3"/></g><g fill="' + fill + '"><path d="M15 5h34v17c0 11.5-7.6 20-17 20S15 33.5 15 22z"/><path d="M28.5 41h7l1.5 11h-10z"/><rect x="21" y="51" width="22" height="6" rx="1.5"/><rect x="16" y="57" width="32" height="10" rx="2.5"/></g><path d="M32 13.5l2.6 5.3 5.8.8-4.2 4.1 1 5.8-5.2-2.7-5.2 2.7 1-5.8-4.2-4.1 5.8-.8z" fill="' + (muted ? "#eef0f3" : "#fff8dc") + '" opacity=".9"/><path d="M20.5 9v12.5c0 6 2.2 10.3 5.5 13" fill="none" stroke="#fff" stroke-width="2.2" stroke-linecap="round" opacity=".45"/></svg>';
  }

  // plugins/csm-standings/src/site/views/gift.ts
  var ID = "gf-";
  var url = (id) => "url(#" + ID + id + ")";
  function stops(list3) {
    return list3.map(
      ([o, c, a]) => '<stop offset="' + o + '" stop-color="' + c + '"' + (a === void 0 ? "" : ' stop-opacity="' + a + '"') + "/>"
    ).join("");
  }
  var linear = (id, x2, y2, s, x1 = "0", y1 = "0") => '<linearGradient id="' + ID + id + '" x1="' + x1 + '" y1="' + y1 + '" x2="' + x2 + '" y2="' + y2 + '">' + s + "</linearGradient>";
  var DEFS = "<defs>" + linear(
    "body",
    "1",
    "1",
    stops([
      ["0", "#e9d9ad"],
      [".5", "#c9ad6a"],
      ["1", "#8f7537"]
    ])
  ) + linear(
    "lid",
    "1",
    "1",
    stops([
      ["0", "#f3e6c2"],
      ["1", "#b39352"]
    ])
  ) + linear(
    "top",
    "0",
    "0",
    stops([
      ["0", "#e2cf9a"],
      ["1", "#f7eed6"]
    ]),
    "0",
    "1"
  ) + linear(
    "shade",
    "0",
    "1",
    stops([
      ["0", "#000", ".55"],
      ["1", "#000", "0"]
    ])
  ) + linear(
    "rib",
    "1",
    "0",
    stops([
      ["0", "#050607"],
      [".45", "#2a2d33"],
      [".55", "#3a3e46"],
      ["1", "#050607"]
    ])
  ) + linear(
    "ribd",
    "0",
    "1",
    stops([
      ["0", "#2a2d33"],
      ["1", "#07080a"]
    ])
  ) + linear(
    "loopl",
    "0",
    "0",
    stops([
      ["0", "#07080a"],
      [".55", "#2a2d33"],
      [".85", "#4d525c"],
      ["1", "#1a1c20"]
    ]),
    "1",
    "1"
  ) + linear(
    "loopr",
    "1",
    "0",
    stops([
      ["0", "#07080a"],
      [".55", "#2a2d33"],
      [".85", "#4d525c"],
      ["1", "#1a1c20"]
    ]),
    "0",
    "1"
  ) + '<radialGradient id="' + ID + 'knot" cx=".4" cy=".35" r=".7">' + stops([
    ["0", "#4d525c"],
    ["1", "#07080a"]
  ]) + "</radialGradient>" + linear(
    "sheen",
    "1",
    "0",
    stops([
      ["0", "#fff", "0"],
      [".5", "#fff", ".35"],
      ["1", "#fff", "0"]
    ])
  ) + linear(
    "beam",
    "0",
    "1",
    stops([
      ["0", "#fff6dc", "0"],
      [".25", "#fff6dc", ".10"],
      ["1", "#f3d77a", ".16"]
    ])
  ) + '<radialGradient id="' + ID + 'spot" cx=".5" cy=".5" r=".5">' + stops([
    ["0", "#f3d77a", ".35"],
    ["1", "#f3d77a", "0"]
  ]) + "</radialGradient>" + linear(
    "plinth",
    "0",
    "1",
    stops([
      ["0", "#2b2f35"],
      ["1", "#0b0c0e"]
    ])
  ) + '<clipPath id="' + ID + 'clip"><rect x="138" y="160" width="324" height="340"/></clipPath><filter id="' + ID + 'blur" x="-20%" y="-20%" width="140%" height="140%"><feGaussianBlur stdDeviation="8"/></filter></defs>';
  var BOX = (
    // The lid's top, then the body, the lid's shadow on it, the lid and its lit edge.
    '<path d="M138 226 L462 226 L448 210 L152 210 Z" fill="' + url("top") + '"/><rect x="152" y="270" width="296" height="230" fill="' + url("body") + '"/><rect x="152" y="276" width="296" height="18" fill="' + url("shade") + '"/><rect x="138" y="226" width="324" height="50" rx="2" fill="' + url("lid") + '"/><rect x="138" y="226" width="324" height="1.5" fill="#fff6dc" opacity=".55"/><rect x="286" y="226" width="28" height="274" fill="' + url("rib") + '"/><path d="M286 226 L314 226 L312 210 L288 210 Z" fill="' + url("rib") + '"/><rect x="286" y="275" width="28" height="3" fill="#000" opacity=".25"/><path d="M296 222 C288 238 280 252 268 268 L276 262 L280 274 C290 258 298 240 302 224 Z" fill="' + url("ribd") + '"/><path d="M304 222 C312 238 320 252 332 268 L324 262 L320 274 C310 258 302 240 298 224 Z" fill="' + url("ribd") + '"/><path d="M300 214 C276 176 214 160 206 190 C200 214 248 222 300 214 Z" fill="' + url("loopl") + '"/><path d="M300 214 C324 176 386 160 394 190 C400 214 352 222 300 214 Z" fill="' + url("loopr") + '"/><path d="M300 214 C268 196 232 190 222 198 C246 206 272 210 300 214 Z" fill="#000" opacity=".22"/><path d="M300 214 C332 196 368 190 378 198 C354 206 328 210 300 214 Z" fill="#000" opacity=".22"/><path d="M226 186 C240 172 268 180 288 200" fill="none" stroke="#fff" stroke-width="1.5" opacity=".35"/><rect x="289" y="203" width="22" height="22" rx="6" fill="' + url("knot") + '"/>'
  );
  var ART = '<svg class="gf-art" viewBox="0 0 600 600" aria-hidden="true" focusable="false">' + DEFS + // The beam from above, the plinth in its light, then the box, scaled onto the plinth, and the light sweeping it.
  '<path class="gf-beam" d="M262 -120 H338 L520 520 H80 Z" fill="' + url("beam") + '" filter="' + url("blur") + '"/><g class="gf-plinth"><ellipse cx="300" cy="520" rx="240" ry="34" fill="' + url("spot") + '"/><path d="M120 500 H480 V528 C480 540 120 540 120 528 Z" fill="' + url("plinth") + '"/><ellipse cx="300" cy="500" rx="180" ry="14" fill="#3a3e46"/><ellipse cx="300" cy="500" rx="180" ry="14" fill="none" stroke="#B8952E" stroke-width="1" opacity=".6"/></g><g transform="translate(90 150) scale(.7)"><g class="gf-box">' + BOX + '<g clip-path="' + url("clip") + '"><rect class="gf-sheen" x="230" y="140" width="120" height="380" fill="' + url("sheen") + '"/></g></g></g></svg>';
  function dust() {
    let s = "";
    for (let i = 0; i < 18; i++)
      s += '<i class="gf-mote" style="left:' + String(40 + (i * 37 + 5) % 40 / 2) + "%;top:" + String(30 + (i * 53 + 5) % 50) + "%;animation-delay:" + (1 + i * 29 % 80 / 10).toFixed(1) + 's"></i>';
    return s;
  }
  function giftSlideHtml(label2) {
    return '<div class="sl sl-gift">' + ART + dust() + '<span class="vh">' + esc(label2) + "</span></div>";
  }

  // plugins/csm-standings/src/site/views/palmares.ts
  function palmaresButton(season) {
    const G = T.gala;
    return '<button type="button" class="gl-entry ub-gala" data-palmares="1"><span class="gl-entry-star" aria-hidden="true">★</span><span><b>' + esc(G.entry(season)) + "</b><small>" + esc(G.title) + '</small></span><span class="gl-entry-go" aria-hidden="true">→</span></button>';
  }
  var detailsButton = (key) => '<button type="button" class="gl-more" data-help="' + key + '" aria-haspopup="dialog" aria-label="' + esc(T.gala.details) + '" title="' + esc(T.gala.details) + '">?</button>';
  function explainDialog(key, f, rulebook) {
    const G = T.gala;
    const rule = rulebook ? '<a class="jlink" href="' + esc(rulebook) + '" target="_blank" rel="noopener">' + esc(G.ruleLink) + " ↗</a>" : esc(G.ruleLink);
    return '<dialog class="help hexplain" data-help-for="' + key + '" aria-labelledby="' + key + '-t"><div class="h-h"><b id="' + key + '-t">' + esc(f.title) + '</b><button type="button" class="h-x" data-help-close="1" aria-label="' + esc(T.close) + '">×</button></div><div class="h-b"><p class="hx-kind">' + esc(f.manual ? G.howPick : G.how) + "</p><p>" + esc(f.explain.text).replace("{rule}", rule) + "</p>" + (f.explain.example ? '<p class="hx-ex">' + esc(f.explain.example) + "</p>" : "") + (f.challenge ? '<p class="hx-defi"><b>' + esc(f.challenge.next ? G.challengeNext : G.challenge) + "</b> " + esc(f.challenge.text) + "</p>" : "") + "</div></dialog>";
  }
  function quitForm(note, action) {
    return '<form class="gl-quit" method="post" action="' + esc(action) + '"><input type="hidden" name="csm_apercu" value="quitter"><span>🔒 ' + esc(note) + '</span><button type="submit">' + esc(T.gala.quit) + "</button></form>";
  }
  function palmaresGate(season, action, wrong) {
    const G = T.gala;
    return '<button type="button" class="back" data-back="1">' + esc(T.back) + '</button><section class="gl-hero gl-gate"><div><p class="eyebrow">' + esc(G.eyebrow(season)) + "</p><h2>" + esc(G.gateTitle) + '</h2><p class="gl-lede">' + esc(G.gateText) + '</p><form method="post" action="' + esc(action) + '"><input type="hidden" name="csm_apercu" value="ouvrir"><label>' + esc(G.gateLabel) + ' <input type="password" name="csm_apercu_code" required autocomplete="current-password"></label><button type="submit" class="gl-play">' + esc(G.gateOpen) + "</button></form>" + (wrong ? '<p class="gl-wrong" role="alert">' + esc(G.gateWrong) + "</p>" : "") + "</div></section>";
  }
  function pathHtml(path, cls) {
    const disc = (c) => "<b>" + esc(c.text) + "</b><span>" + esc(c.short) + "</span>";
    return '<ol class="gl-path' + (cls === null ? " big" : "") + '">' + path.map(
      (c) => '<li class="' + c.kind + '">' + (c.race && cls !== null ? '<button type="button" class="gl-pl"' + eventAttrs(c.race) + ' data-final="' + esc(cls) + '" aria-label="' + esc(T.event.open(c.race.event, dateRange3(c.race))) + '">' + disc(c) + "</button>" : disc(c)) + "</li>"
    ).join("") + "</ol>";
  }
  var ARROW = '<i class="gl-arr" aria-hidden="true">›</i>';
  function classCard(r) {
    const G = T.gala;
    const head = '<header><button type="button" class="gl-go" data-go="' + esc(r.champ.code) + '" data-cls="' + esc(r.k.name) + '"><b>' + esc(r.k.name) + "</b>" + ARROW + "</button><span>" + esc(G.riders(r.riders)) + " · " + esc(G.racesDone(r.done, r.planned)) + "</span></header>";
    if (r.blocked) return '<article class="gl-card">' + head + '<p class="notice">' + esc(T.updating) + "</p></article>";
    const [c, ...rest] = r.podium;
    return '<article class="gl-card">' + head + '<div class="gl-champion"><span class="gl-star" aria-hidden="true">★</span><div>' + who(c.name, "gl-name") + '<span class="gl-meta">' + esc(T.plate) + " " + esc(c.no) + " · <b>" + String(c.pts) + esc(G.pts) + "</b> · " + esc(G.lead(r.lead)) + "</span></div></div>" + pathHtml(r.path, r.k.name) + (rest.length ? '<ol class="gl-rest">' + rest.map(
      (s) => '<li><span class="rp m' + String(s.rank) + '">' + String(s.rank) + "</span>" + who(s.name) + "<em>" + String(s.pts) + esc(G.pts) + "</em></li>"
    ).join("") + "</ol>" : "") + "</article>";
  }
  function trophyHead(f, key) {
    if (!f.trophy) return "";
    const muted = !!f.challenge;
    return '<div class="tr-head">' + trophySvg(key + "-cup", muted) + '<span class="tr-pill' + (muted ? " muted" : "") + '">' + esc(muted ? T.gala.trophyToWin : T.gala.trophy) + "</span></div>";
  }
  function factCard(f, key) {
    const G = T.gala;
    if (f.challenge)
      return '<article class="gl-fact defi' + (f.trophy ? " trophy to-win" : "") + '">' + detailsButton(key) + trophyHead(f, key) + '<span class="gl-defi">' + esc(f.challenge.next ? G.challengeNext : G.challenge) + '</span><span class="lab">' + esc(f.title) + '</span><div class="gl-fv goal"><small class="obj">' + esc(G.goal) + "</small><b>" + esc(f.challenge.goal) + "</b><small>" + esc(f.challenge.unit) + "</small></div><p>" + esc(f.challenge.text) + "</p></article>";
    const detail = f.detail ? "<p>" + esc(f.detail) + "</p>" : "";
    let body;
    if (f.duels)
      body = detail + '<ol class="gl-flist gl-duels">' + f.duels.map(
        (d) => '<li><button type="button" class="lab gl-go" data-go="' + esc(d.code) + '" data-cls="' + esc(d.k) + '">' + esc(d.cls) + ARROW + "</button><span>" + who(d.a) + " <b>" + String(d.ap) + esc(T.gala.pts) + "</b></span><span>" + esc(T.gala.ahead) + who(d.b) + " <b>" + String(d.bp) + esc(T.gala.pts) + "</b></span>" + (d.why ? "<em>" + esc(d.why) + "</em>" : "") + "</li>"
      ).join("") + "</ol>";
    else if (f.list)
      body = detail + '<ol class="gl-flist">' + f.list.map((x) => "<li>" + who(x.name) + "<em>" + esc(x.note) + "</em></li>").join("") + "</ol>";
    else
      body = '<div class="gl-fw">' + // The biggest start names finals, the organizers' highlights free text, the others riders (a name opens their page).
      f.who.map((n) => f.code === "FIELD" || f.manual ? "<span>" + esc(n) + "</span>" : who(n)).join("") + "</div>" + detail;
    return '<article class="gl-fact' + (f.trophy ? " trophy" : "") + '">' + detailsButton(key) + trophyHead(f, key) + '<span class="lab">' + esc(f.title) + "</span>" + // An organizers' highlight may have no figure (VALUE empty): its names right under its title.
    (f.value ? '<div class="gl-fv"><b>' + esc(f.value) + "</b><small>" + esc(f.unit) + "</small></div>" : "") + body + "</article>";
  }
  function champSection(model, c) {
    const ch = c.champ;
    const phase = phaseOf(ch.done, ch.total);
    return '<section class="gl-champ" id="gl-' + esc(ch.code) + '" style="--c:' + esc(ch.color) + '"><header class="gl-ch">' + // The logo opens the championship too, a second way to the name's button (hidden from the keyboard and readers).
    (ch.logo ? '<button type="button" class="gl-logo" data-sel="' + esc(ch.code) + '" tabindex="-1" aria-hidden="true">' + logo(ch) + "</button>" : "") + '<div><h3><button type="button" class="gl-go" data-sel="' + esc(ch.code) + '">' + esc(ch.name) + " " + esc(model.season) + ARROW + "</button></h3>" + (c.final ? statusHtml(phase, T.gala.final) : statusHtml(phase, T.phase[phase], T.gala.provisionalTag(ch.done, ch.total))) + '</div></header><div class="gl-grid">' + c.classes.map(classCard).join("") + "</div></section>";
  }
  function palmaresPage(model, p, quit) {
    const G = T.gala;
    const open = p.champs.filter((c) => !c.final).map((c) => c.champ.code);
    return '<button type="button" class="back" data-back="1">' + esc(T.back) + '</button><div class="gl-hero"><div><p class="eyebrow">' + esc(G.eyebrow(p.season)) + "</p><h2>" + esc(G.title) + '</h2><p class="gl-lede">' + esc(G.lede(p.season)) + "</p>" + quit + '</div><div class="gl-playbox"><button type="button" class="gl-play" data-play="0"><span aria-hidden="true">▶</span> ' + esc(G.play) + "</button><small>" + esc(G.playHint) + "</small></div></div>" + (open.length ? '<p class="gl-warn">' + esc(G.provisional(open)) + "</p>" : "") + '<div class="pstats gl-figs">' + p.figures.map(
      (f) => "<div><b>" + String(f.value) + (f.of !== void 0 ? '<small style="font-size:16px"> / ' + String(f.of) + "</small>" : "") + "</b><span>" + esc(f.label) + "</span></div>"
    ).join("") + '</div><nav class="gl-jump">' + p.champs.map(
      (c) => '<button type="button" class="chip" style="--c:' + esc(c.champ.color) + '" data-jump="' + esc(c.champ.code) + '">' + esc(c.champ.code) + "</button>"
    ).join("") + "</nav>" + // The highlights off (HIGHLIGHTS in error): no section.
    (p.facts.length ? sectionHead(G.factsTitle, G.factsAside) + '<div class="gl-facts">' + p.facts.map(
      (f, i) => factCard(f, "hlc-" + String(i)) + explainDialog("hlc-" + String(i), f, model.rules.rulebookUrl)
    ).join("") + "</div>" : "") + p.champs.map((c) => champSection(model, c)).join("");
  }

  // plugins/csm-standings/src/site/views/sponsors.ts
  var GOLD = "#B8952E";
  function logoIn(s, w, h) {
    if (s.image === null) return '<span class="sp-name-only">' + esc(s.name) + "</span>";
    const ratio = s.image.w / s.image.h;
    const ink = Math.min(1.25, Math.max(0.85, Math.sqrt(0.5 / Math.max(s.image.ink, 0.05))));
    let lw = Math.sqrt(w * h * 0.3 * ink * ratio);
    let lh = lw / ratio;
    const fit2 = Math.min(1, w * 0.84 / lw, h * 0.7 / lh);
    lw *= fit2;
    lh *= fit2;
    return '<img src="' + esc(s.image.url) + '" alt="' + esc(s.name) + '" style="width:' + (100 * lw / w).toFixed(2) + "%;height:" + (100 * lh / h).toFixed(2) + '%">';
  }
  function plate2(s, w, h, i, caption) {
    return '<figure class="sp-item" style="--i:' + String(i) + '"><div class="sp-plate" style="width:' + String(w) + "px;height:" + String(h) + "px;background:" + esc(s.image?.plate ?? "#ffffff") + '">' + logoIn(s, w, h) + "</div>" + caption + "</figure>";
  }
  function sparks() {
    let s = '<div class="sp-sparks" aria-hidden="true">';
    for (let i = 0; i < 26; i++)
      s += '<i style="left:' + String(i * 37 % 100) + "%;animation-delay:" + (i * 53 % 100 / 18).toFixed(2) + "s;animation-duration:" + (4 + i * 29 % 10 / 3).toFixed(2) + 's"></i>';
    return s + "</div>";
  }
  var WIDTH = 1420;
  function fitGrid(counts, look) {
    for (let w = look.w; ; w = Math.floor(w * 0.94)) {
      const h = Math.round(w * look.h / look.w);
      const room = Math.max(1, Math.floor((WIDTH + look.gapX) / (w + look.gapX)));
      const cols = w === look.w ? Math.min(look.cols, room) : room;
      const rows3 = counts.reduce((sum, n) => sum + Math.ceil(n / cols), 0);
      const height = rows3 * (h + look.caption) + (rows3 - counts.length) * look.gapY + counts.length * (look.head + look.gapY);
      if (height <= look.height || w < 60) return { w, h, cols };
    }
  }
  var slide = (cls, inner) => '<div class="sl sp ' + cls + '">' + sparks() + inner + "</div>";
  function sinceText(since, season, long) {
    const S = T.gala.show.sponsors;
    const n = Number(season) - since + 1;
    if (n <= 1) return S.firstSeason;
    return long ? S.sinceLong(since, n) : S.since(since);
  }
  function sponsorSlides(season, sponsors) {
    if (!sponsors.length) return [];
    const S = T.gala.show.sponsors;
    const base = { group: S.group, color: GOLD, part: "end", sub: S.group, places: 0, details: "" };
    const of = (t) => sponsors.filter((s) => s.tier === t);
    const slides = [
      {
        ...base,
        label: S.openLabel,
        html: () => slide(
          "sp-open",
          '<div class="sp-openin"><span class="sl-kicker">★ ' + esc(S.group) + "</span><h1>" + esc(S.title[0]) + "<br>" + esc(S.title[1]) + '</h1><span class="sl-season">' + esc(S.sub(sponsors.length, season)) + '</span><div class="sp-ghosts" aria-hidden="true">' + sponsors.map(
            (s, i) => '<span style="--i:' + String(i) + ";background:" + esc(s.image?.plate ?? "#ffffff") + '">' + logoIn(s, 120, 60) + "</span>"
          ).join("") + "</div></div>"
        )
      }
    ];
    for (const s of of("PLATINUM"))
      slides.push({
        ...base,
        label: s.name,
        html: () => slide(
          "sp-solo",
          '<span class="sl-kicker">' + esc(S.one(S.tier.PLATINUM)) + "</span>" + plate2(s, 980, 460, 0, "") + '<h2 class="sp-name">' + esc(s.name) + "</h2>" + (s.since === null ? "" : '<span class="sp-since">' + esc(sinceText(s.since, season, true)) + "</span>")
        )
      });
    const caption = (s) => "<figcaption><b>" + esc(s.name) + "</b>" + (s.since === null ? "" : "<small>" + esc(sinceText(s.since, season, false)) + "</small>") + "</figcaption>";
    const grid = (t, look, cls) => {
      const list3 = of(t);
      if (!list3.length) return;
      const { w, h, cols } = fitGrid([list3.length], look);
      slides.push({
        ...base,
        label: S.many(S.tier[t]),
        html: () => slide(
          "sp-grid " + cls,
          '<span class="sl-kicker">' + esc(S.many(S.tier[t])) + '</span><div class="sp-cells" style="--cols:' + String(Math.min(cols, list3.length)) + '">' + list3.map((s, i) => plate2(s, w, h, i, caption(s))).join("") + "</div>"
        )
      });
    };
    grid("GOLD", { w: 400, h: 196, cols: 3, gapX: 30, gapY: 26, caption: 54, head: 0, height: 680 }, "sp-gold");
    grid("SILVER", { w: 250, h: 124, cols: 5, gapX: 30, gapY: 26, caption: 40, head: 0, height: 680 }, "sp-silver");
    const onWall = ["BRONZE", "A LA CARTE", null].filter((t) => of(t).length);
    const named = onWall.flatMap((t) => t ? [S.tier[t]] : []);
    if (onWall.length)
      slides.push({
        ...base,
        label: S.wallLabel(named),
        html: () => {
          let i = 0;
          const counts = onWall.map((t) => of(t).length);
          const fit2 = fitGrid(counts, { w: 184, h: 92, cols: 4, gapX: 14, gapY: 12, caption: 19, head: 28, height: 690 });
          const { w, h } = fit2;
          const best2 = w === 184;
          const block = (t, cols) => {
            const list3 = of(t);
            return list3.length ? '<section class="sp-block">' + (t ? "<h3>" + esc(S.tier[t]) + "</h3>" : "") + '<div class="sp-cells" style="--cols:' + String(Math.min(cols, list3.length)) + '">' + list3.map((s) => plate2(s, w, h, i++, "<figcaption><b>" + esc(s.name) + "</b></figcaption>")).join("") + "</div></section>" : "";
          };
          return slide(
            "sp-wall",
            '<span class="sl-kicker">' + esc(S.wall(named)) + "</span>" + block("BRONZE", best2 ? 6 : fit2.cols) + block("A LA CARTE", best2 ? 4 : fit2.cols) + block(null, best2 ? 4 : fit2.cols)
          );
        }
      });
    return slides;
  }
  function sponsorLoop(season, sponsors, clubLogos) {
    if (!sponsors.length) return [];
    const S = T.gala.show.sponsors;
    const high = sponsors.filter((s) => s.tier === "PLATINUM" || s.tier === "GOLD" || s.tier === "SILVER");
    const low = sponsors.filter((s) => !high.includes(s));
    const row2 = (list3, w, h, cls) => {
      if (!list3.length) return "";
      const times = Math.ceil(1600 / ((w + 26) * list3.length));
      const items = list3.map((s, i) => plate2(s, w, h, i, "")).join("");
      return '<div class="sp-row ' + cls + '"><div class="sp-track">' + items.repeat(times * 2) + "</div></div>";
    };
    return [
      {
        group: S.group,
        color: GOLD,
        part: "end",
        sub: S.group,
        places: 0,
        details: "",
        label: S.loopLabel,
        html: () => slide(
          "sp-loop",
          '<header class="sp-loophead"><span class="sl-kicker">★ ' + esc(S.group) + "</span><h2>" + esc(S.loopTitle) + '</h2><span class="sl-season">' + esc(T.gala.show.openSeason(season)) + "</span></header>" + row2(high, 300, 148, "sp-left") + row2(low, 210, 104, "sp-right") + '<footer class="sp-club"><span>' + esc(T.gala.show.brand) + '</span><div class="sl-logos">' + clubLogos + "</div></footer>"
        )
      }
    ];
  }

  // plugins/csm-standings/src/site/welcome-data.ts
  var INTEGER2 = /^\d+$/;
  var COORDINATES = /^\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*$/;
  function pointOf2(e) {
    const m = COORDINATES.exec(e.place?.point ?? "");
    return m ? { lat: Number(m[1]), lng: Number(m[2]) } : null;
  }
  var days = (a, b) => Math.round((Date.parse(b) - Date.parse(a)) / 864e5) + 1;
  function top(items) {
    const m = /* @__PURE__ */ new Map();
    for (const x of items) m.set(x, (m.get(x) ?? 0) + 1);
    let best2 = null;
    for (const [k, n] of m) if (n >= 2 && (!best2 || n > best2[1])) best2 = [k, n];
    return best2;
  }
  function most(items, size) {
    let best2 = null;
    for (const x of items) if (best2 === null || size(x) > size(best2)) best2 = x;
    return best2;
  }
  function welcomeOf(model) {
    const finals = finalsOf(model);
    const galaRank = new Map(model.champs.map((c, i) => [c.code, i]));
    const byGala = (codes) => [...codes].sort((x, y) => galaRank.get(x) - galaRank.get(y));
    const entries = (name2) => model.people.get(name2);
    const order = model.gala.classes;
    const took = new Set(finals.flatMap((f) => [...f.tookPart]));
    const riders = [...took].sort((a, b) => a.localeCompare(b)).map((name2) => ({ name: name2, no: entries(name2)[0].no }));
    const counts = model.champs.flatMap(
      (c) => c.classes.map((k) => ({ c, name: k.name, riders: k.riders.filter((r) => participated(r, c.events)).length }))
    );
    const champs = model.champs.filter((c) => c.logo).map((c) => {
      const own2 = counts.filter((x) => x.c === c);
      return {
        champ: c,
        classes: (order ? inListOrder(own2, order).items : own2).map(({ name: name2, riders: n }) => ({ name: name2, riders: n }))
      };
    });
    const races = model.calendar.map((e) => {
      const run = finals.filter((f) => f.e === e && f.tookPart.size > 0);
      return {
        e,
        codes: byGala(e.codes),
        coming: e.start > model.today,
        riders: new Set(run.flatMap((f) => [...f.tookPart])).size,
        classes: new Set(run.map((f) => f.cls)).size
      };
    });
    const cal = model.calendar;
    const facts = [];
    const plates = [...model.people.values()].map((es) => [...new Set(es.map((x) => x.no))]);
    const plate3 = top(plates.flat().filter((x) => x !== ""));
    if (plate3) facts.push({ code: "plate", no: plate3[0], riders: plate3[1] });
    const first = top(riders.map((r) => r.name.split(/\s+/)[0]));
    if (first) facts.push({ code: "firstName", name: first[0], riders: first[1] });
    const last = top(riders.map((r) => r.name.split(/\s+/).slice(1).join(" ")).filter((x) => x !== ""));
    if (last) facts.push({ code: "lastName", name: last[0], riders: last[1] });
    const popular = most(
      counts.filter((x) => x.riders > 0),
      (x) => x.riders
    );
    if (popular) facts.push({ code: "popular", cls: popular.name, champ: popular.c.name, riders: popular.riders });
    if (cal.length)
      facts.push({
        code: "season",
        start: cal[0].start,
        end: cal[cal.length - 1].end,
        days: days(cal[0].start, cal[cal.length - 1].end),
        raceDays: cal.reduce((n, e) => n + (e.raceDays.length || days(e.start, e.end)), 0)
      });
    let points2 = 0;
    for (const c of model.champs)
      for (const k of c.classes)
        for (const r of k.riders)
          for (const ev of c.events) {
            const v = r[ev];
            if (INTEGER2.test(v)) points2 += Number(v);
          }
    if (points2) facts.push({ code: "points", points: points2 });
    const month = top(cal.map((e) => Number(e.start.slice(5, 7))));
    if (month) facts.push({ code: "month", month: month[0], races: month[1] });
    const gaps = cal.slice(1).map((e, i) => ({ a: cal[i], b: e, gap: days(cal[i].end, e.start) - 2 }));
    const rest = most(gaps, (g) => g.gap);
    if (rest && rest.gap > 7) facts.push({ code: "rest", a: rest.a.venue, b: rest.b.venue, days: rest.gap });
    const pair = gaps.find((g) => g.gap <= 0);
    if (pair) facts.push({ code: "pair", a: pair.a.venue, b: pair.b.venue, start: pair.a.start, end: pair.b.end });
    const weekend = most(cal, (e) => days(e.start, e.end));
    if (weekend && days(weekend.start, weekend.end) >= 3)
      facts.push({ code: "weekend", venue: weekend.venue, days: days(weekend.start, weekend.end) });
    const nums = [...new Set(plates.flat())].filter((x) => INTEGER2.test(x)).map(Number);
    if (nums.length >= 2) facts.push({ code: "plates", low: Math.min(...nums), high: Math.max(...nums) });
    const multi = [...took].filter((n) => new Set(entries(n).map((x) => x.code)).size > 1).length;
    if (multi) facts.push({ code: "multi", riders: multi });
    const host = top(cal.map((e) => e.venue).filter((v) => v !== ""));
    if (host) facts.push({ code: "host", venue: host[0], races: host[1] });
    return { riders, champs, races, facts };
  }

  // plugins/csm-standings/src/site/views/welcome-map.ts
  var MAP_W = 680;
  var MAP_H = 230;
  var MAP_PAD = 26;
  var MIN_GAP = 56;
  var CODE_CH = 10;
  var KM_CH = 8.4;
  var pct = (x) => Math.min(100, Math.max(0, x)).toFixed(3) + "%";
  var sec = (s) => s.toFixed(2) + "s";
  var windowKeyframes = (name2, a, b) => "@keyframes " + name2 + "{0%," + pct(a - 0.01) + "{opacity:0}" + pct(a + 0.6) + "," + pct(b - 0.6) + "{opacity:1}" + pct(b) + ",100%{opacity:0}}";
  function boxAt(cx, cy, text, perChar, h) {
    const w = text.length * perChar + 6;
    return [cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2];
  }
  function layout(places) {
    const keys = [...places.keys()];
    const spread = (val, size, flip) => {
      const vs3 = keys.map((k) => val(places.get(k)));
      const [lo, hi] = [Math.min(...vs3), Math.max(...vs3)];
      const sorted = [...new Set(vs3)].sort((a, b) => a - b);
      return vs3.map((v) => {
        const t = sorted.length > 1 ? 0.5 * ((v - lo) / (hi - lo)) + 0.5 * (sorted.indexOf(v) / (sorted.length - 1)) : 0.5;
        return MAP_PAD + (flip ? 1 - t : t) * (size - 2 * MAP_PAD);
      });
    };
    const xs = spread((q) => q.lng, MAP_W, false);
    const ys = spread((q) => q.lat, MAP_H, true);
    const pos = new Map(keys.map((k, j) => [k, [xs[j], ys[j]]]));
    const list3 = [...pos.values()];
    for (let it = 0; it < 80; it++)
      for (const a of list3)
        for (const b of list3) {
          const dx = b[0] - a[0];
          const dy = b[1] - a[1];
          const d = Math.hypot(dx, dy);
          if (a === b || d >= MIN_GAP) continue;
          const [ux, uy] = d > 0 ? [dx / d, dy / d] : [1, 0];
          const push = (MIN_GAP - d) / 2;
          a[0] -= ux * push;
          a[1] -= uy * push;
          b[0] += ux * push;
          b[1] += uy * push;
        }
    for (const q of list3) {
      q[0] = Math.min(MAP_W - 16, Math.max(16, q[0]));
      q[1] = Math.min(MAP_H - 16, Math.max(16, q[1]));
    }
    return pos;
  }
  function mapHtml(season, calendar, distances, at2, slot, loop, kf) {
    const W = T.gala.show.welcome;
    const placed = calendar.flatMap((e, i) => {
      const pt = pointOf2(e);
      return pt ? [{ i, pt, key: e.place.code }] : [];
    });
    const places = new Map(placed.map((x) => [x.key, x.pt]));
    if (places.size < 2) return "";
    const pos = layout(places);
    const taken = [...pos.values()].map(([x, y]) => [x - 9, y - 9, x + 9, y + 9]);
    const free = (b) => b[0] >= 2 && b[1] >= 2 && b[2] <= MAP_W - 2 && b[3] <= MAP_H - 2 && taken.every((t) => b[2] <= t[0] || b[0] >= t[2] || b[3] <= t[1] || b[1] >= t[3]);
    const codeAt = /* @__PURE__ */ new Map();
    for (const [key, [x, y]] of pos) {
      const half = (key.length * CODE_CH + 6) / 2;
      const tries = [
        [x, y + 22],
        [x, y - 20],
        [x + 14 + half, y],
        [x - 14 - half, y],
        [x + 10 + half, y + 18],
        [x - 10 - half, y + 18],
        [x + 10 + half, y - 18],
        [x - 10 - half, y - 18]
      ];
      const spot = tries.find(([cx, cy]) => free(boxAt(cx, cy, key, CODE_CH, 16))) ?? tries[0];
      taken.push(boxAt(spot[0], spot[1], key, CODE_CH, 16));
      codeAt.set(key, spot);
    }
    const anim = (name2) => ' style="animation:' + name2 + " " + sec(loop) + ' linear infinite"';
    const xy = (p) => p.map((v) => v.toFixed(1));
    let segs = "";
    let kms = "";
    let dots = "";
    let codes = "";
    let curs = "";
    const coded = /* @__PURE__ */ new Set();
    const stretches = stretchesOf(calendar).map((s) => kmOf(distances, s));
    const total = stretches.every((d) => d !== null) ? stretches.reduce((sum, d) => sum + d, 0) : null;
    const byEvent = new Map(placed.map((p) => [calendar[p.i], p]));
    const legs = stretchesOf(calendar).flatMap((s) => {
      const [from, to] = [byEvent.get(s.from), byEvent.get(s.to)];
      return from && to ? [{ from, to, d: kmOf(distances, s) }] : [];
    });
    legs.forEach(({ from, to, d }, n) => {
      const [px, py] = pos.get(from.key);
      const [x, y] = pos.get(to.key);
      const a = at2(to.i);
      kf.push(
        "@keyframes wl-seg" + String(to.i) + "{0%," + pct(a) + "{stroke-dashoffset:1}" + pct(a + slot * 0.25) + ",99.6%{stroke-dashoffset:0}100%{stroke-dashoffset:1}}"
      );
      const [x1, y1] = xy([px, py]);
      const [x2, y2] = xy([x, y]);
      segs += '<line pathLength="1" x1="' + x1 + '" y1="' + y1 + '" x2="' + x2 + '" y2="' + y2 + '"' + anim("wl-seg" + String(to.i)) + "/>";
      if (d === null) return;
      const text = W.km(d);
      const len = Math.max(1, Math.hypot(x - px, y - py));
      const nx = -(y - py) / len;
      const ny = (x - px) / len;
      const spots = [13, 24].flatMap(
        (off) => [0.5, 0.4, 0.6, 0.3, 0.7, 0.2, 0.8].flatMap(
          (t) => [1, -1].map((side) => [
            px + (x - px) * t + side * nx * off,
            py + (y - py) * t + side * ny * off
          ])
        )
      );
      const [kx, ky] = spots.find(([cx, cy]) => free(boxAt(cx, cy, text, KM_CH, 17))) ?? [
        (x + px) / 2 + nx * 13,
        (y + py) / 2 + ny * 13
      ];
      const next = legs[n + 1];
      kf.push(windowKeyframes("wl-km" + String(to.i), a + slot * 0.25, next ? at2(next.to.i) + slot * 0.25 : 99.6));
      kms += '<text class="wl-km" x="' + Math.min(MAP_W - 30, Math.max(30, kx)).toFixed(1) + '" y="' + (ky + 5).toFixed(1) + '" text-anchor="middle"' + anim("wl-km" + String(to.i)) + ">" + esc(text) + "</text>";
    });
    for (const { i, key } of placed) {
      const a = at2(i);
      kf.push(windowKeyframes("wl-vis" + String(i), a + slot * 0.2, 99.6));
      kf.push(windowKeyframes("wl-cur" + String(i), a, a + slot));
      const [cx, cy] = xy(codeAt.get(key));
      const ty = (Number(cy) + 5).toFixed(1);
      if (!coded.has(key)) {
        coded.add(key);
        codes += '<text class="wl-code" x="' + cx + '" y="' + ty + '" text-anchor="middle"' + anim("wl-vis" + String(i)) + ">" + esc(key) + "</text>";
      }
      const [x, y] = xy(pos.get(key));
      const c = '" cx="' + x + '" cy="' + y;
      dots += '<circle class="wl-dot' + c + '" r="6"/><circle class="wl-vis' + c + '" r="7"' + anim("wl-vis" + String(i)) + "/>";
      curs += '<g class="wl-cur"' + anim("wl-cur" + String(i)) + '><circle class="wl-ring' + c + '" r="16"/><text x="' + cx + '" y="' + ty + '" text-anchor="middle">' + esc(key) + "</text></g>";
    }
    return '<figure class="wl-map"><figcaption><span class="sl-kicker">' + esc(W.tour(season)) + "</span><span>" + esc(W.tourSum(calendar.length, total)) + '</span></figcaption><svg viewBox="0 0 ' + String(MAP_W) + " " + String(MAP_H) + '" aria-hidden="true"><g class="wl-segs">' + segs + "</g>" + dots + kms + codes + curs + "</svg></figure>";
  }

  // plugins/csm-standings/src/site/views/welcome.ts
  var CARD_S = 9;
  var LOGO_S = 8;
  var SCROLL_HOLD_S = 1.8;
  var WALL_SPEED = 30;
  var CHIP_PX = 330;
  var TWO_ROWS = 16;
  var pct2 = (x) => Math.min(100, Math.max(0, x)).toFixed(3) + "%";
  var sec2 = (s) => s.toFixed(2) + "s";
  function slotKeyframes(name2, n, total, fade, from, to) {
    const slot = 100 / n;
    const f = 100 * fade / total;
    return "@keyframes " + name2 + "{0%{opacity:0;transform:" + from + "}" + pct2(f) + "{opacity:1;transform:none}" + pct2(slot - f) + "{opacity:1;transform:none}" + pct2(slot) + "{opacity:0;transform:" + to + "}100%{opacity:0;transform:" + to + "}}";
  }
  function litKeyframes(n, total) {
    const slot = 100 / n;
    const f = 100 * 0.6 / total;
    const off = "{transform:none;box-shadow:0 10px 30px rgba(0,0,0,.4),0 0 0 0 transparent}";
    return "@keyframes wl-lit{0%,100%" + off + pct2(f) + "," + pct2(slot - f) + "{transform:translateY(-5px) scale(1.08);box-shadow:0 14px 40px rgba(0,0,0,.5),0 0 0 4px var(--lc),0 0 50px 6px var(--lc)}" + pct2(slot) + off + "}";
  }
  var fit = (text, max, width) => Math.max(26, Math.min(max, Math.floor(width / (text.length * 0.6))));
  var classesSize = (names) => Math.max(18, Math.min(22, Math.floor(1340 / (names.reduce((n, k) => n + k.length + 7, 0) * 0.56))));
  function scrollKeyframes(n, total) {
    const at2 = (s) => pct2(100 * s / total);
    return "@keyframes wl-ckscroll{0%," + at2(0.6 + SCROLL_HOLD_S) + "{transform:none}" + at2(LOGO_S - 0.6 - SCROLL_HOLD_S) + "," + pct2(100 / n) + "{transform:translateX(min(0px,calc(1420px - 100%)))}}";
  }
  function factTexts(f) {
    const F = T.gala.show.welcome.facts;
    switch (f.code) {
      case "plate":
        return F.plate(f.no, f.riders);
      case "firstName":
        return F.firstName(f.name, f.riders);
      case "lastName":
        return F.lastName(f.name, f.riders);
      case "popular":
        return F.popular(f.cls, f.champ, f.riders);
      case "season":
        return F.season(f.start, f.end, f.days, f.raceDays);
      case "points":
        return F.points(f.points);
      case "month":
        return F.month(f.month, f.races);
      case "rest":
        return F.rest(f.a, f.b, f.days);
      case "pair":
        return F.pair(f.a, f.b, f.start, f.end);
      case "weekend":
        return F.weekend(f.venue, f.days);
      case "plates":
        return F.plates(f.low, f.high);
      case "multi":
        return F.multi(f.riders);
      case "host":
        return F.host(f.venue, f.races);
    }
  }
  var logoOr = (c) => c.logo ? '<span class="sl-logo"><img src="' + esc(c.logo) + '" alt="' + esc(c.code) + '"></span>' : '<span class="wl-rc">' + esc(c.code) + "</span>";
  function raceCard2(model, r, i, style) {
    const W = T.gala.show.welcome;
    const e = r.e;
    const name2 = e.venue || e.event;
    const champs = r.codes.map((code) => model.byCode.get(code));
    return '<li class="wl-card wl-race" style="' + // Its first championship's color (the CSS has the gold for a race without one).
    champs.slice(0, 1).map((c) => "--rc:" + esc(c.color) + ";").join("") + style + '"><span class="wl-rk">' + esc(W.race(i + 1, model.calendar.length)) + (r.coming ? ' <em class="wl-soon">' + esc(W.soon) + "</em>" : "") + '</span><b class="wl-rv" style="--vs:' + String(fit(name2, 52, 500)) + 'px">' + esc(name2) + '</b><span class="wl-rd">' + esc(dateRange3(e) + " · " + e.event) + "</span>" + // Its figures in bold.
    (r.riders ? '<span class="wl-rn">' + esc(W.started(r.riders, r.classes)).replace(/\d+/g, (d) => "<b>" + d + "</b>") + "</span>" : "") + '<span class="wl-rl">' + champs.map(logoOr).join("") + "</span></li>";
  }
  function welcomeHtml(model) {
    const W = T.gala.show.welcome;
    const w = welcomeOf(model);
    const deck = [];
    let f = 0;
    w.races.forEach((_, i) => {
      deck.push({ race: i });
      if (f < w.facts.length) deck.push({ fact: f++ });
    });
    while (f < w.facts.length) deck.push({ fact: f++ });
    const loop = Math.max(1, deck.length) * CARD_S;
    const slot = 100 / Math.max(1, deck.length);
    const raceAt = new Map(deck.flatMap((d, j) => "race" in d ? [[d.race, j]] : []));
    const style = (j) => "animation-duration:" + sec2(loop) + ";animation-delay:" + sec2(j * CARD_S);
    const cards3 = deck.map((d, j) => {
      if ("race" in d) return raceCard2(model, w.races[d.race], d.race, style(j));
      const [big, sentence] = factTexts(w.facts[d.fact]);
      return '<li class="wl-card wl-know" style="' + style(j) + '"><span class="wl-rk">' + esc(W.know) + '</span><b class="wl-kb" style="--ks:' + String(fit(big, 80, 580)) + 'px">' + esc(big) + '</b><span class="wl-kt">' + esc(sentence) + "</span></li>";
    }).join("");
    const kf = [];
    const map = mapHtml(
      model.season,
      model.calendar,
      model.distances,
      (i) => raceAt.get(i) * slot,
      slot,
      loop,
      kf
    );
    const litS = Math.max(1, w.champs.length) * LOGO_S;
    const lit = (i) => ";animation-duration:" + sec2(litS) + ";animation-delay:" + sec2(i * LOGO_S);
    const logos = w.champs.map(
      ({ champ: c }, i) => '<span class="sl-logo wl-logo" style="--lc:' + esc(c.color) + lit(i) + '"><img src="' + esc(c.logo) + '" alt="' + esc(c.code) + '"></span>'
    ).join("");
    const classes = w.champs.map(({ champ: c, classes: ks }, i) => {
      const counted = ks.some((k) => k.riders > 0);
      return '<li class="wl-cl" style="--lc:' + esc(c.color) + lit(i) + '"><span class="wl-cn"><b>' + esc(c.name) + "</b>" + (counted ? "<em>" + esc(W.perClass) + "</em>" : "") + '</span><span class="wl-ck" style="font-size:' + String(classesSize(ks.map((k) => k.name))) + "px" + lit(i) + '">' + ks.map((k) => "<span>" + esc(k.name) + (counted ? "<small>" + String(k.riders) + "</small>" : "") + "</span>").join("") + "</span></li>";
    }).join("");
    const chip2 = (r) => '<span class="wl-chip">' + (r.no ? "<b>" + esc(r.no) + "</b>" : "") + esc(r.name) + "</span>";
    const rows3 = w.riders.length >= TWO_ROWS ? [w.riders.filter((_, i) => i % 2 === 0), w.riders.filter((_, i) => i % 2 === 1)] : [w.riders];
    const wall = rows3.map((row2, j) => {
      const chips = row2.map(chip2).join("");
      return '<div class="wl-row' + (j === 1 ? " back" : "") + '"><div class="wl-track" style="animation-duration:' + sec2(Math.max(30, row2.length * CHIP_PX / WALL_SPEED)) + '">' + chips + '<span class="wl-copy" aria-hidden="true">' + chips + "</span></div></div>";
    }).join("");
    let sparks2 = '<div class="wl-sparks" aria-hidden="true">';
    for (let i = 0; i < 28; i++)
      sparks2 += '<i style="left:' + String(i * 37 % 100) + "%;animation-delay:" + sec2(-(i * 53 % 140) / 10) + ";animation-duration:" + sec2(12 + i * 29 % 60 / 10) + ";--s:" + String(3 + i * 17 % 5) + 'px"></i>';
    sparks2 += "</div>";
    return '<div class="sl wl' + (map ? "" : " nomap") + // The background's two lights: the first two championships' colors (the CSS has the gold without them).
    '" style="' + w.champs.slice(0, 2).map((c, i) => "--w" + "ab".charAt(i) + ":" + esc(c.champ.color) + ";").join("") + '"><style>' + slotKeyframes("wl-deck", Math.max(1, deck.length), loop, 0.8, "translateX(40px)", "translateX(-40px)") + litKeyframes(Math.max(1, w.champs.length), litS) + slotKeyframes("wl-cls", Math.max(1, w.champs.length), litS, 0.6, "translateY(8px)", "translateY(-8px)") + scrollKeyframes(Math.max(1, w.champs.length), litS) + kf.join("") + '</style><div class="wl-amb" aria-hidden="true"><i class="wl-blob a"></i><i class="wl-blob b"></i><i class="wl-stripes"></i></div>' + sparks2 + '<header class="wl-head"><span class="sl-kicker">' + esc(T.gala.show.brand) + "</span><h1>" + esc(W.title) + '</h1><span class="sl-season">' + esc(W.sub(model.season)) + '</span></header><div class="sl-logos wl-logos">' + logos + '</div><ul class="wl-classes">' + classes + '</ul><div class="wl-mid">' + map + '<ol class="wl-deck">' + cards3 + "</ol></div>" + (w.riders.length ? '<section class="wl-wall"><span class="sl-kicker wl-wk">' + esc(W.wall) + "</span>" + wall + "</section>" : "") + '<p class="sl-keys">' + esc(W.keys) + '</p><p class="sl-keys touch">' + esc(W.touchKeys) + "</p></div>";
  }

  // plugins/csm-standings/src/site/views/show.ts
  var DETAILS = "hls";
  var GOLD2 = "#B8952E";
  var REVEAL_STEP = 1.5;
  var REVEAL_TAIL = 1.3;
  var revealMs = (left) => left > 0 ? Math.round((left * REVEAL_STEP + REVEAL_TAIL) * 1e3) : 0;
  var logoPlate = (src, alt) => src ? '<span class="sl-logo"><img src="' + esc(src) + '" alt="' + esc(alt) + '"></span>' : "";
  var sec3 = (s) => s.toFixed(2) + "s";
  function confetti(color, delay = 0) {
    const colors = [color, GOLD2, "#f3d77a", "#ffffff"];
    let s = '<div class="sl-confetti" aria-hidden="true">';
    for (let i = 0; i < 90; i++) {
      const x = i * 37 % 100;
      const d = i * 53 % 100 / 100;
      s += '<i style="left:' + String(x) + "%;background:" + esc(colors[i % colors.length]) + ";animation-delay:" + sec3(delay + d * 1.2) + ";animation-duration:" + (2.4 + i * 29 % 10 / 6).toFixed(2) + "s;--r:" + String(i * 71 % 360) + 'deg"></i>';
    }
    return s + "</div>";
  }
  function podiumSlide(model, r) {
    const G = T.gala;
    const ch = r.champ;
    const n = r.podium.length;
    const tag = r.final ? "" : '<span class="sl-prov">' + esc(G.provisionalTag(r.done, r.planned)) + "</span>";
    const top2 = '<header class="sl-top">' + logoPlate(ch.logo, ch.code) + '<div><span class="sl-kicker">' + esc(ch.name) + '</span><h2 class="sl-class">' + esc(r.k.name) + '</h2><span class="sl-sub">' + esc(G.riders(r.riders) + " · " + G.racesDone(r.done, r.planned)) + "</span></div>" + tag + "</header>";
    const base = { label: r.k.name, group: ch.code, color: ch.color, part: ch.code, sub: "", details: "" };
    if (r.blocked)
      return {
        ...base,
        places: 0,
        html: () => '<div class="sl sl-podium">' + top2 + '<p class="sl-notice">' + esc(T.updating) + "</p></div>"
      };
    return {
      ...base,
      // A class that isn't blocked has a rider (palmares-data.ts classResult): there is always a champion.
      places: n,
      html: (shown2, auto) => {
        const left = n - shown2;
        const whole = left === 0;
        const at2 = (rank) => (left - rank + 1) * REVEAL_STEP;
        const col = (rank) => {
          if (rank > n) return '<div class="sl-col empty"></div>';
          const s = r.podium[rank - 1];
          const isIn = rank > left;
          return '<div class="sl-col r' + String(rank) + (isIn || auto ? " on" : "") + // Already in while the others come: it doesn't come in again.
          (isIn && !whole ? " in" : "") + '"' + (!isIn && auto ? ' style="--d:' + sec3(at2(rank)) + '"' : "") + '><div class="sl-who">' + (rank === 1 ? '<span class="sl-crown">' + esc(r.final ? G.title1 + " " + ch.code + " " + model.season : G.leading + " · " + ch.code) + "</span>" : "") + '<span class="sl-name">' + esc(s.name) + '</span><span class="sl-pts"><b>' + String(s.pts) + "</b>" + esc(G.pts) + (rank === 1 ? " · " + esc(G.lead(r.lead)) : " · " + esc(T.plate) + " " + esc(s.no)) + '</span></div><div class="sl-step"><span>' + ord(rank) + "</span></div></div>";
        };
        const d = !whole && auto ? ' style="--d:' + sec3(at2(1)) + '"' : "";
        return '<div class="sl sl-podium' + // Crowned: the champion is in, or comes in by itself.
        (whole || auto ? " crowned" : "") + (whole ? "" : auto ? " auto" : " clicks") + '">' + top2 + '<div class="sl-stand"' + d + ">" + col(2) + col(1) + col(3) + '</div><div class="sl-pathbox"' + d + ">" + pathHtml(r.path, null) + "</div>" + (d ? confetti(ch.color, at2(1)) : "") + "</div>";
      }
    };
  }
  function revealPlace(canvas, shown2, n, color) {
    const promo = canvas.querySelector(".sl-promo");
    if (promo) {
      revealPromotion(promo);
      return;
    }
    const fact = canvas.querySelector(".sl-fact.trophy");
    if (fact) {
      fact.classList.replace("hold", "won");
      fact.insertAdjacentHTML("beforeend", confetti(color));
      return;
    }
    canvas.querySelector(".sl-col.r" + String(n - shown2)).classList.add("on");
    if (shown2 + 1 < n) return;
    const podium2 = canvas.querySelector(".sl-podium");
    podium2.classList.add("crowned");
    podium2.insertAdjacentHTML("beforeend", confetti(color));
  }
  function trophyStage(f) {
    if (!f.trophy) return "";
    const muted = !!f.challenge;
    return '<div class="tr-stage">' + (muted ? "" : '<span class="tr-rays" aria-hidden="true"></span>') + trophySvg("sl-cup", muted) + '</div><span class="tr-crown' + (muted ? " muted" : "") + '">' + esc(muted ? T.gala.trophyToWin : T.gala.trophy) + "</span>";
  }
  function factSlide(f, shown2, auto) {
    const G = T.gala;
    if (f.challenge)
      return '<div class="sl sl-fact sl-defi' + (f.trophy ? " trophy to-win" : "") + '"><span class="sl-kicker">' + esc(f.challenge.next ? G.challengeNext : G.challenge) + "</span><h2>" + esc(f.title) + "</h2>" + trophyStage(f) + '<div class="sl-big goal"><small class="obj">' + esc(G.goal) + "</small><b>" + esc(f.challenge.goal) + "</b><small>" + esc(f.challenge.unit) + '</small></div><p class="sl-detail sl-defit">' + esc(f.challenge.text) + "</p></div>";
    const detail = f.detail ? '<p class="sl-detail">' + esc(f.detail) + "</p>" : "";
    let body;
    if (f.duels)
      body = detail + '<ol class="sl-duels">' + f.duels.map(
        (d) => '<li><span class="sl-dcls">' + esc(d.cls) + '</span><div class="sl-dpair"><span class="win"><b>' + esc(d.a) + "</b><small>" + String(d.ap) + esc(T.gala.pts) + '</small></span><span class="vs">' + esc(T.gala.ahead.trim()) + "</span><span><b>" + esc(d.b) + "</b><small>" + String(d.bp) + esc(T.gala.pts) + "</small></span></div>" + (d.why ? '<span class="sl-dwhy">' + esc(d.why) + "</span>" : "") + "</li>"
      ).join("") + "</ol>";
    else if (f.list)
      body = detail + '<ol class="sl-list' + (f.list.length > 8 ? " cols" : "") + '">' + f.list.map((x) => "<li><b>" + esc(x.name) + "</b><span>" + esc(x.note) + "</span></li>").join("") + "</ol>";
    else body = '<div class="sl-names">' + f.who.map((n) => "<span>" + esc(n) + "</span>").join("") + "</div>" + detail;
    const waiting = !!f.trophy && shown2 === 0;
    return '<div class="sl sl-fact' + (f.list || f.duels ? " has-list" : "") + (f.trophy ? " trophy" + (waiting ? auto ? " auto won" : " hold" : " won whole") : "") + '"' + (waiting && auto ? ' style="--d:' + sec3(REVEAL_STEP) + '"' : "") + '><span class="sl-kicker">' + esc(G.factsTitle) + "</span><h2>" + esc(f.title) + "</h2>" + trophyStage(f) + // No figure (an organizers' highlight without VALUE): nothing counts up.
    (f.value ? '<div class="sl-big"><b data-count="' + esc(f.value) + '">' + esc(f.value) + "</b><small>" + esc(f.unit) + "</small></div>" : "") + body + (waiting && auto ? confetti(GOLD2, REVEAL_STEP) : "") + "</div>";
  }
  function openerHtml(season, title, line, extra) {
    return '<div class="sl sl-champ sl-section"><span class="sl-kicker">★ ' + esc(T.gala.show.openSeason(season)) + "</span><h2>" + esc(title) + '</h2><span class="sl-sub">' + esc(line) + "</span>" + extra + "</div>";
  }
  var openerLogos = (p) => '<div class="sl-logos">' + p.champs.map(
    (c, i) => c.champ.logo ? '<span class="sl-logo" style="--i:' + String(i) + '"><img src="' + esc(c.champ.logo) + '" alt="' + esc(c.champ.code) + '"></span>' : ""
  ).join("") + "</div>";
  function slidesOf(model, p) {
    const S = T.gala.show;
    const O = S.openers;
    const logos = model.champs.map((c) => logoPlate(c.logo, c.code)).join("");
    const season = { group: S.group, color: GOLD2, part: "start", sub: "", places: 0, details: "" };
    const slides = [
      { ...season, label: S.welcome.label, uncounted: true, html: () => welcomeHtml(model) },
      {
        ...season,
        label: S.openLabel,
        html: () => '<div class="sl sl-open"><span class="sl-kicker">' + esc(S.brand) + "</span><h1>" + esc(S.open) + '</h1><span class="sl-season">' + esc(S.openSeason(p.season)) + '</span><div class="sl-logos">' + logos + '</div><p class="sl-keys">' + esc(S.keys) + '</p><p class="sl-keys touch">' + esc(S.touchKeys) + "</p></div>"
      },
      {
        ...season,
        label: S.statsLabel,
        html: () => '<div class="sl sl-stats"><h2>' + esc(S.stats(p.season)) + '</h2><div class="sl-figs">' + p.figures.map(
          (f) => '<div><b data-count="' + String(f.value) + '">' + String(f.value) + "</b>" + (f.of !== void 0 ? "<small>" + esc(S.of(f.of)) + "</small>" : "") + "<span>" + esc(f.label) + "</span></div>"
        ).join("") + "</div></div>"
      }
    ];
    const podiums = p.champs.reduce((sum, c) => sum + c.classes.length, 0);
    if (p.champs.length)
      slides.push({
        ...season,
        label: O.podiumsLabel,
        html: () => openerHtml(p.season, O.podiums, O.podiumsLine(p.champs.length, podiums), openerLogos(p))
      });
    for (const c of p.champs) {
      const ch = c.champ;
      slides.push({
        label: ch.code,
        group: ch.code,
        color: ch.color,
        part: ch.code,
        sub: "",
        places: 0,
        details: "",
        html: () => '<div class="sl sl-champ">' + logoPlate(ch.logo, ch.code) + "<h2>" + esc(ch.name) + '</h2><span class="sl-season">' + esc(S.openSeason(p.season)) + '</span><span class="sl-sub">' + esc(S.champCounts(c.classes.length, c.riders, ch.total)) + "</span>" + (c.final ? "" : '<span class="sl-prov">' + esc(T.gala.provisionalTag(ch.done, ch.total)) + "</span>") + "</div>"
      });
      for (const r of c.classes) slides.push(podiumSlide(model, r));
    }
    if (p.facts.length)
      slides.push({
        label: T.gala.factsTitle,
        group: T.gala.factsTitle,
        color: GOLD2,
        part: "end",
        sub: T.gala.factsTitle,
        places: 0,
        details: "",
        html: () => openerHtml(
          p.season,
          T.gala.factsTitle,
          O.factsLine(p.facts.length),
          '<ul class="sl-teasers">' + p.facts.map(
            (f, i) => '<li style="--i:' + String(i) + '">' + // A trophy goes to it: a small cup before its title.
            (f.trophy && !f.challenge ? '<span class="tr-mini">' + trophySvg("tz-" + String(i)) + "</span>" : "") + esc(f.title) + "</li>"
          ).join("") + "</ul>"
        )
      });
    for (const f of p.facts)
      slides.push({
        label: f.title,
        group: T.gala.factsTitle,
        color: GOLD2,
        part: "end",
        sub: T.gala.factsTitle,
        places: f.trophy && !f.challenge ? 1 : 0,
        html: (shown2, auto) => factSlide(f, shown2, auto),
        details: explainDialog(DETAILS, f, model.rules.rulebookUrl)
      });
    const promotions = model.promotions ?? [];
    if (promotions.length) {
      const P = S.promotions;
      const base = { group: P.group, color: GOLD2, part: "end", sub: P.group, details: "" };
      slides.push({
        ...base,
        label: P.openLabel,
        places: 0,
        html: () => openerHtml(nextSeason(p.season), P.group, P.sub(promotions.length), disciplinesHtml(promotions))
      });
      for (const r of promotions)
        slides.push({
          ...base,
          label: P.label(r.discipline, levelName(r.from), levelName(r.to)),
          places: 1,
          html: (shown2, auto) => promotionHtml(r, p.season, shown2, auto, sec3(REVEAL_STEP))
        });
    }
    slides.push({
      label: S.giftsLabel,
      group: S.giftsLabel,
      color: GOLD2,
      part: "end",
      sub: S.giftsLabel,
      places: 0,
      details: "",
      html: () => giftSlideHtml(S.giftsLabel)
    });
    const final = p.champs.every((c) => c.final);
    const titles = p.champs.reduce((sum, c) => sum + c.classes.filter((r) => r.podium.length).length, 0);
    if (p.champs.length)
      slides.push({
        label: final ? O.championsLabel : O.leadersLabel,
        group: S.champsTitle(p.season),
        color: GOLD2,
        part: "end",
        sub: S.champsTitle(p.season),
        places: 0,
        details: "",
        html: () => openerHtml(
          p.season,
          final ? O.champions(p.season) : O.leaders(p.season),
          final ? O.championsLine(titles, p.champs.length) : O.leadersLine(titles, p.champs.length),
          openerLogos(p)
        )
      });
    for (const c of p.champs) {
      const ch = c.champ;
      slides.push({
        label: ch.code,
        group: S.champsTitle(p.season),
        color: ch.color,
        part: "end",
        sub: S.champsTitle(p.season),
        places: 0,
        details: "",
        html: () => '<div class="sl sl-wall"><header class="sl-top">' + logoPlate(ch.logo, ch.code) + '<div><span class="sl-kicker">' + esc(c.final ? S.champsTitle(p.season) : S.leadersTitle(p.season)) + '</span><h2 class="sl-class">' + esc(ch.name) + '</h2></div></header><ol class="sl-wallgrid">' + c.classes.map(
          (r) => "<li><span>" + esc(r.k.name) + "</span><b>" + // A blocked class, or one without a rider yet: no name.
          esc(r.podium.length ? r.podium[0].name : S.noName) + "</b></li>"
        ).join("") + "</ol></div>"
      });
    }
    slides.push(...sponsorSlides(p.season, model.sponsors));
    slides.push({
      ...season,
      part: "end",
      label: S.endLabel,
      html: () => '<div class="sl sl-open"><span class="sl-kicker">' + esc(S.brand) + "</span><h1>" + esc(S.end) + '</h1><span class="sl-season">' + esc(S.endSub(p.season)) + '</span><div class="sl-logos">' + logos + "</div></div>"
    });
    slides.push(...sponsorLoop(p.season, model.sponsors, logos));
    return slides;
  }
  function stageHtml(slides, i, shown2, auto) {
    const S = T.gala.show;
    const s = slides[i];
    const counted = slides.filter((x) => !x.uncounted);
    const n = counted.indexOf(s) + 1;
    return '<div class="gl-canvas" data-next="1">' + s.html(shown2, auto) + '</div><p class="gl-rotate">' + esc(S.rotate) + '</p><div class="gl-bar"><button type="button" class="gl-nav" data-step="-1" aria-label="' + esc(S.prev) + '"' + (i === 0 ? " disabled" : "") + '>‹</button><span class="gl-where">' + esc(s.group + (s.group === s.label ? "" : " · " + s.label)) + '</span><span class="gl-count">' + (s.uncounted ? "" : String(n) + " / " + String(counted.length)) + '</span><button type="button" class="gl-nav" data-step="1" aria-label="' + esc(S.next) + '"' + (i === slides.length - 1 ? " disabled" : "") + '>›</button><i style="width:' + (100 * n / counted.length).toFixed(2) + '%"></i></div><div class="gl-tools">' + // A highlight: « i » opens its box (I), which never moves the show on.
    (s.details ? '<button type="button" class="gl-info" data-help="' + DETAILS + '" title="' + esc(T.gala.detailsKey) + '" aria-label="' + esc(T.gala.details) + '">i</button>' : "") + modeButton(auto) + '<button type="button" data-toc="1" title="' + esc(S.tocKey) + '" aria-label="' + esc(S.toc) + '">☰</button><button type="button" data-fs="1" title="' + esc(S.fullscreen) + '" aria-label="' + esc(S.fullscreen) + '">⛶</button><button type="button" data-quit="1" title="' + esc(S.closeKey) + '" aria-label="' + esc(S.close) + '">×</button></div>' + s.details;
  }
  function modeButton(auto) {
    const S = T.gala.show;
    return '<button type="button" class="gl-mode" data-mode="1" aria-pressed="' + String(auto) + '" title="' + esc(S.modeLabel) + '">' + esc(auto ? S.modeAuto : S.modePlace) + "</button>";
  }
  function tocHtml(model, slides, i, open) {
    const S = T.gala.show;
    const current2 = slides[i].part;
    const item = (j, text, cls) => '<button type="button" class="' + cls + (j < i ? " seen" : "") + '" data-go="' + String(j) + '"' + (j === i ? ' aria-current="true"' : "") + ">" + esc(text) + "</button>";
    const parts = [];
    slides.forEach((s, j) => {
      const last = parts[parts.length - 1];
      if (last?.key === s.part) last.last = j;
      else parts.push({ key: s.part, first: j, last: j });
    });
    let n = 0;
    const blocks = parts.map((p) => {
      const isOpen = open === "all" || p.key === current2;
      const range = slides.slice(p.first, p.last + 1);
      let head;
      let items = "";
      let color = GOLD2;
      if (p.key === "start" || p.key === "end") {
        head = '<span class="gl-th"><span class="gl-tt">' + esc(p.key === "start" ? S.tocStart : S.tocEnd) + '</span><small class="gl-tn">' + esc(S.tocScreens(range.length)) + "</small></span>";
        let sub = "";
        range.forEach((s, k) => {
          if (s.sub && s.sub !== sub) items += '<h5 class="gl-tsub">' + esc(sub = s.sub) + "</h5>";
          items += item(p.first + k, s.label, "gl-ti");
        });
      } else {
        const ch = model.byCode.get(p.key);
        color = range[0].color;
        n++;
        head = '<kbd class="gl-tk">' + // Keys 1 to 9 only: from the 10th championship on, the key stays empty (hidden).
        "123456789".charAt(n - 1) + "</kbd>" + (ch.logo ? '<img class="gl-tl" src="' + esc(ch.logo) + '" alt="">' : "") + '<span class="gl-th"><span class="gl-tt">' + esc(ch.name) + '</span><small class="gl-tn">' + esc(T.categories(range.length - 1)) + "</small></span>";
        items = item(p.first, S.tocChamp, "gl-ti wide");
        range.slice(1).forEach((s, k) => {
          items += item(p.first + 1 + k, s.label, "gl-ti");
        });
      }
      return '<details class="gl-tb' + (p.key === current2 ? " here" : "") + '" style="--c:' + esc(color) + '"' + (isOpen ? " open" : "") + "><summary>" + head + '</summary><div class="gl-tis">' + items + "</div></details>";
    });
    return '<div class="gl-toc" role="dialog" aria-label="' + esc(S.toc) + '"><header><b>' + esc(S.toc) + '</b><span class="gl-thelp">' + esc(S.tocHelp) + '</span><button type="button" data-toc="0" aria-label="' + esc(S.closeToc) + '">×</button></header><div class="gl-tbody">' + blocks.join("") + "</div></div>";
  }

  // plugins/csm-standings/src/site/check.ts
  function mountVerification(root, payload) {
    const body = root.querySelector(".csm-check-body");
    const workbook = workbookOf(payload, siteLanguages());
    const parts = partsOf(workbook, payload);
    const page = { payload, cards: 0 };
    const html = parts.map((p) => partHtml(p, page)).join("");
    const issues3 = parts.flatMap((p) => p.sections.flatMap((s) => s.issues));
    const errors = issues3.filter((i) => i.severity === "error").length;
    body.innerHTML = summaryHtml(parts, errors, issues3.length - errors, page.cards) + html;
    const win = root.ownerDocument.defaultView;
    const chips = [...body.querySelectorAll(".ck-chip")];
    const wanted = () => {
      const tab = new URL(win.location.href).searchParams.get("tab");
      return chips.find((c) => c.dataset.go === tab) ?? chips.find((c) => c.classList.contains("err")) ?? chips[0];
    };
    selectTab(body, wanted().dataset.go);
    win.addEventListener("popstate", () => {
      selectTab(body, wanted().dataset.go);
    });
    const counter = body.querySelector("[data-done-count]");
    body.addEventListener("change", (event) => {
      const box = event.target;
      if (!box.matches("[data-done]")) return;
      const card3 = box.closest(".ck-card");
      card3.classList.toggle("is-done", box.checked);
      openCard(card3, !box.checked);
      counter.textContent = T.cards.doneCount(
        body.querySelectorAll("[data-done]:checked").length,
        page.cards
      );
    });
    body.addEventListener("click", (event) => {
      const target = event.target;
      const toggle = target.closest("[data-toggle]");
      if (toggle) {
        const card3 = toggle.closest(".ck-card");
        openCard(card3, !card3.classList.contains("is-open"));
        return;
      }
      const link = target.closest("[data-go]");
      if (!link) return;
      event.preventDefault();
      const id = link.getAttribute("data-go");
      selectTab(body, id);
      const url2 = new URL(win.location.href);
      url2.searchParams.set("tab", id);
      win.history.pushState(null, "", url2.href);
      body.querySelector(".ck-bar").scrollIntoView({ block: "start" });
    });
  }
  function selectTab(body, id) {
    for (const chip2 of body.querySelectorAll(".ck-chip")) {
      const on = chip2.dataset.go === id;
      chip2.setAttribute("aria-selected", String(on));
      body.querySelector("#ck-" + chip2.dataset.go).hidden = !on;
    }
  }
  function openCard(card3, open) {
    const done = card3.classList.contains("is-done");
    const toggle = card3.querySelector("[data-toggle]");
    card3.classList.toggle("is-open", done && open);
    toggle.hidden = !done;
    toggle.setAttribute("aria-expanded", String(!done || open));
    toggle.textContent = open ? T.cards.fold : T.cards.unfold;
  }
  function partsOf(workbook, payload) {
    const formats = (tab) => workbook.issues.filter(
      (i) => i.code === "number-format" && payload.formats.some((f) => f.tab === tab && f.row === i.row && f.col === i.col)
    );
    const general = [
      {
        id: "seasons",
        title: T.cards.seasonsTab,
        tab: "SEASONS",
        issues: [...workbook.seasonsTab.issues, ...formats("SEASONS")],
        ctx: { kind: "seasons" },
        codes: [],
        tabs: []
      },
      {
        id: "venues",
        title: T.cards.venuesTab,
        tab: "VENUES",
        issues: [...workbook.venues.issues, ...formats("VENUES")],
        ctx: { kind: "venues" },
        codes: [],
        tabs: []
      },
      {
        id: "config",
        title: T.cards.configTab,
        tab: "CONFIG",
        issues: [...workbook.config.issues, ...formats("CONFIG")],
        ctx: { kind: "config" },
        codes: [],
        tabs: []
      },
      {
        id: "calendar",
        title: T.cards.calendarTab,
        tab: "CALENDAR",
        issues: [...workbook.calendar.issues, ...formats("CALENDAR")],
        ctx: { kind: "calendar" },
        codes: [],
        tabs: []
      }
    ];
    if (workbook.gala !== null)
      general.push({
        id: "gala",
        title: T.check.gala,
        tab: "GALA",
        issues: workbook.gala,
        ctx: { kind: "gala" },
        codes: [],
        tabs: []
      });
    if (workbook.highlights !== null)
      general.push({
        id: "highlights",
        title: T.check.highlights,
        tab: "HIGHLIGHTS",
        issues: workbook.highlights,
        ctx: { kind: "highlights" },
        codes: [],
        tabs: []
      });
    if (workbook.sponsors !== null)
      general.push({
        id: "sponsors",
        title: T.check.sponsors,
        tab: "SPONSORS",
        issues: workbook.sponsors,
        ctx: { kind: "sponsors" },
        codes: [],
        tabs: []
      });
    if (workbook.promotions !== null)
      general.push({
        id: "promotions",
        title: T.check.promotions,
        tab: "PROMOTIONS",
        issues: workbook.promotions,
        ctx: { kind: "promotions" },
        codes: [],
        tabs: []
      });
    if (workbook.mediaKit !== null)
      general.push({
        id: "media-kit",
        title: T.check.mediaKit,
        tab: MEDIA_KIT_TAB,
        issues: workbook.mediaKit,
        ctx: { kind: "mediaKit" },
        codes: [],
        tabs: []
      });
    if (workbook.distances !== null)
      general.push({
        id: "distances",
        title: T.check.distances,
        tab: "DISTANCES",
        issues: workbook.distances,
        ctx: { kind: "distances" },
        codes: [],
        tabs: []
      });
    const parts = [
      {
        id: "workbook",
        title: T.cards.workbookPart,
        nav: T.cards.workbookNav,
        sections: general,
        blocked: workbook.blocked,
        extra: placesHtml(workbook)
      }
    ];
    if (workbook.blocked) return parts;
    for (const season of [...workbook.seasons].sort(naturalOrder)) {
      const data = seasonOf(workbook, payload, season);
      const codes = data.championships.map((c) => c.entry.code);
      const tabs2 = data.championships.map((c) => c.entry.tab);
      const sections = data.championships.map(({ entry, standings }) => ({
        id: season + "-" + entry.code,
        title: T.check.tab(entry.tab, entry.code),
        tab: entry.tab,
        issues: standings ? standings.issues : [],
        notShown: standings === null,
        ctx: { kind: "tab", code: entry.code, season },
        codes,
        tabs: tabs2
      }));
      sections.push({
        id: season + "-between",
        title: T.check.between,
        help: T.cards.betweenHelp,
        tab: "",
        issues: data.crossIssues,
        ctx: { kind: "cross", season },
        codes,
        tabs: tabs2
      });
      parts.push({
        id: season,
        title: T.check.season(season),
        nav: season,
        sections,
        blocked: false,
        extra: galaHtml(workbook, payload, season)
      });
    }
    return parts;
  }
  function summaryHtml(parts, errors, warnings, cards3) {
    const nav = parts.map((p) => {
      const n = p.sections.reduce((sum, s) => sum + s.issues.length, 0);
      const bad = p.blocked || p.sections.some((s) => s.notShown === true || s.issues.some((i) => i.severity === "error"));
      const kind = bad ? "err" : n ? "warn" : "ok";
      return '<a href="#" role="tab" class="ck-chip ' + kind + '" aria-controls="ck-' + esc(p.id) + '" data-go="' + esc(p.id) + '">' + esc(p.nav) + "<span>" + (n ? String(n) : bad ? "!" : "✓") + "</span></a>";
    }).join("");
    const counts = errors + warnings === 0 ? '<span class="ck-n ok">' + esc(T.check.none) + "</span>" : (errors ? '<span class="ck-n err">' + esc(T.check.errors(errors)) + "</span>" : "") + (warnings ? '<span class="ck-n warn">' + esc(T.check.warnings(warnings)) + "</span>" : "") + '<span class="ck-n done" data-done-count>' + esc(T.cards.doneCount(0, cards3)) + "</span>";
    return '<div class="ck-bar"><div class="ck-counts">' + counts + '</div><nav class="ck-nav" role="tablist" aria-label="' + esc(T.cards.goTo) + '">' + nav + '</nav></div><p class="ck-intro">' + esc(T.cards.intro) + "</p>";
  }
  function partHtml(part, page) {
    const shown2 = part.sections.filter((s) => s.issues.length || s.notShown);
    const clean = part.sections.filter((s) => !shown2.includes(s));
    return '<section class="ck-part" role="tabpanel" id="ck-' + esc(part.id) + '"><h3 class="csm-check-season">' + esc(part.title) + "</h3>" + (part.blocked ? '<p class="ck-alert">' + esc(T.check.blockedWorkbook) + "</p>" : "") + shown2.map((s) => sectionHtml(s, page)).join("") + (clean.length ? '<p class="ck-clean"><span aria-hidden="true">✓</span> ' + esc(shown2.length ? T.cards.clean : T.cards.allClean) + " " + clean.map((s) => "<b>" + esc(s.title) + "</b>").join(", ") + "</p>" : "") + part.extra + "</section>";
  }
  function placesHtml(workbook) {
    const places = workbook.venues.entries;
    if (!places.length) return "";
    const P = T.cards.places;
    return '<div class="ck-sec ck-places"><h4>' + esc(P.title) + ' <span class="ck-sc">' + esc(P.aside(places.length)) + '</span></h4><p class="ck-sechelp">' + esc(P.help) + '</p><div class="ck-tw"><table class="ck-table"><thead><tr>' + [P.head.code, P.head.name, P.head.address, P.head.map].map((h) => '<th scope="col">' + esc(h) + "</th>").join("") + "</tr></thead><tbody>" + places.map(
      (v) => '<tr><th scope="row">' + esc(v.code) + "</th><td>" + esc(v.name) + "</td><td>" + esc(v.address) + "</td><td>" + (v.point ? '<a href="' + esc(mapUrl(v.point)) + '" target="_blank" rel="noopener">' + esc(P.see) + "</a>" : "<em>" + esc(P.noPoint) + "</em>") + "</td></tr>"
    ).join("") + "</tbody></table></div></div>";
  }
  function trophyMark(id, muted) {
    return ' <span class="hg-st tr' + (muted ? " muted" : "") + '">' + trophySvg("hg-cup-" + id, muted) + esc(muted ? T.gala.trophyToWin : T.gala.trophy) + "</span>";
  }
  function galaHtml(workbook, payload, season) {
    if (!workbook.galaEntries.some((g) => g.season === season)) return "";
    const P = T.check.galaPart;
    const model = modelOf(seasonOf(workbook, payload, season), payload.today);
    const { model: show, palmares: p } = galaOrdered(model, palmaresOf(model));
    const slides = slidesOf(show, p).filter((s) => !s.uncounted);
    const names = (all) => all.slice(0, 3).join(", ") + (all.length > 3 ? T.gala.and(all.length - 3) : "");
    const champs = p.champs.map(
      (c) => '<tr><th scope="row">' + esc(c.champ.code) + (c.final ? "" : "<small>" + esc(P.provisional(c.champ.done, c.champ.total)) + "</small>") + "</th><td>" + c.classes.map(
        (r) => r.blocked ? '<span class="hg-k blocked"><b>' + esc(r.k.name) + "</b> " + esc(P.updating) + "</span>" : (
          // A class that isn't blocked has its champion (or leader: ★).
          '<span class="hg-k"><b>' + esc(r.k.name) + "</b> " + esc(r.podium[0].name) + (r.final ? "" : " ★") + "</span>"
        )
      ).join("") + "</td></tr>"
    ).join("");
    let facts;
    if (model.highlights === null) facts = '<p class="ck-alert">' + esc(P.off) + "</p>";
    else {
      let n = 0;
      const rows3 = model.highlights.map((r) => {
        const title = r.manual ? manualTexts(r.manual).TITLE : factTitle(r.code);
        let state;
        let now;
        let mark = "";
        if (!r.show) {
          state = '<span class="hg-st hidden">' + esc(P.hidden) + "</span>";
          now = P.hiddenWhy;
        } else {
          const f = p.facts[n];
          if (f.trophy) mark = trophyMark(season + "-" + String(n), !!f.challenge);
          if (f.challenge) {
            state = '<span class="hg-st defi">' + esc(P.challenge) + "</span>";
            now = f.challenge.text;
          } else {
            state = f.manual ? '<span class="hg-st pick">' + esc(P.pick) + "</span>" : '<span class="hg-st on">' + esc(P.shown) + "</span>";
            const who2 = f.list ? f.list.map((x) => x.name) : f.duels ? f.duels.map((d) => d.a) : f.who;
            now = (f.value ? f.value + " " + f.unit + " · " : "") + names(who2);
          }
        }
        return "<tr" + (r.show ? "" : ' class="off"') + '><td class="hg-n">' + (r.show ? String(++n) : "") + '</td><th scope="row">' + esc(title) + "<small>" + esc(r.code) + "</small></th><td>" + state + mark + "</td><td>" + esc(now) + "</td></tr>";
      });
      facts = '<div class="ck-tw"><table class="ck-table hg-facts"><thead><tr>' + [P.head.n, P.head.fact, P.head.state, P.head.now].map((h) => '<th scope="col">' + esc(h) + "</th>").join("") + "</tr></thead><tbody>" + rows3.join("") + "</tbody></table></div>";
    }
    return '<div class="ck-sec ck-gala"><h4>' + esc(P.title(season)) + ' <span class="ck-sc">' + esc(P.aside) + '</span></h4><p class="ck-sechelp">' + esc(P.help(slides.length)) + '</p><h5 class="ck-sub">' + esc(P.podiums) + '</h5><div class="ck-tw"><table class="ck-table hg-champs"><thead><tr><th scope="col">' + esc(P.champ) + '</th><th scope="col">' + esc(P.classes) + "</th></tr></thead><tbody>" + champs + '</tbody></table></div><h5 class="ck-sub">' + esc(P.facts) + "</h5>" + facts + promotionsHtml(model.promotions) + '<h5 class="ck-sub">' + esc(P.sponsors) + "</h5>" + sponsorsHtml(model.sponsors) + "</div>";
  }
  function sponsorsHtml(sponsors) {
    const P = T.check.galaPart;
    if (!sponsors.length) return '<p class="ck-alert">' + esc(P.sponsorsNone) + "</p>";
    const rows3 = [...TIERS, null].flatMap((tier) => {
      const list3 = sponsors.filter((s) => s.tier === tier);
      return list3.length ? [
        '<tr><th scope="row">' + esc(tier ? T.gala.show.sponsors.tier[tier] : P.noTier) + "<small>" + String(list3.length) + "</small></th><td>" + list3.map(
          (s) => '<span class="hg-k"><b>' + esc(s.name) + "</b>" + (s.image ? "" : " " + esc(P.noLogo)) + "</span>"
        ).join("") + "</td></tr>"
      ] : [];
    });
    return '<div class="ck-tw"><table class="ck-table hg-champs"><thead><tr><th scope="col">' + esc(P.sponsorsHead.tier) + '</th><th scope="col">' + esc(P.sponsorsHead.partners) + "</th></tr></thead><tbody>" + rows3.join("") + "</tbody></table></div>";
  }
  function promotionsHtml(promotions) {
    const P = T.check.galaPart;
    const H = P.promotionsHead;
    const head = '<h5 class="ck-sub">' + esc(P.promotions) + "</h5>";
    if (promotions === null) return head + '<p class="ck-alert">' + esc(P.promotionsOff) + "</p>";
    if (!promotions.length) return "";
    return head + '<div class="ck-tw"><table class="ck-table hg-champs"><thead><tr>' + [H.discipline, H.rider, H.plates, H.passage].map((h) => '<th scope="col">' + esc(h) + "</th>").join("") + "</tr></thead><tbody>" + promotions.map(
      (p) => "<tr><td>" + esc(p.discipline) + '</td><th scope="row">' + esc(p.name) + "</th><td>" + esc(p.plates.join(", ")) + "</td><td>" + esc(levelName(p.from) + " → " + levelName(p.to)) + "</td></tr>"
    ).join("") + "</tbody></table></div>";
  }
  function headingOf(group, ctx) {
    if (ctx.kind === "tab") return group.issue.category ?? T.check.general;
    if (ctx.kind !== "cross") return "";
    if (group.kind === "names") return group.category;
    if (group.kind !== "one") return group.event + " · " + group.category;
    const issue = group.issue;
    return "event" in issue ? issue.event + " · " + issue.category : T.cards.namesAndColumns;
  }
  function sectionHtml(section, page) {
    let html = '<div class="ck-sec"><h4>' + esc(section.title) + countHtml(section.issues) + "</h4>" + (section.help ? '<p class="ck-sechelp">' + esc(section.help) + "</p>" : "");
    if (section.notShown) return html + '<p class="ck-sechelp">' + esc(T.check.tabNotShown) + "</p></div>";
    const rank = (g) => g.severity === "error" ? 0 : 1;
    const general = (h) => h === T.check.general ? 0 : 1;
    const cards3 = groupIssues(section.issues).map((group, i) => ({ group, i, heading: headingOf(group, section.ctx) })).sort(
      (a, b) => rank(a.group) - rank(b.group) || (section.ctx.kind === "tab" ? general(a.heading) - general(b.heading) || naturalOrder(a.heading, b.heading) : 0) || a.i - b.i
    );
    let heading = null;
    let severity = null;
    for (const { group, heading: h } of cards3) {
      if (group.severity !== severity) {
        severity = group.severity;
        heading = null;
        const kind = severity === "error" ? "err" : "warn";
        html += '<p class="ck-kind ' + kind + '">' + esc(severity === "error" ? T.cards.errorsTitle : T.cards.warningsTitle) + "</p>";
      }
      if (h && h !== heading) html += '<h5 class="ck-sub">' + esc(h) + "</h5>";
      heading = h;
      html += cardHtml(cardOf(group, { ...section, payload: page.payload }), page);
    }
    return html + "</div>";
  }
  function countHtml(issues3) {
    if (!issues3.length) return "";
    const errors = issues3.filter((i) => i.severity === "error").length;
    const warnings = issues3.length - errors;
    return ' <span class="ck-sc">' + (errors ? '<i class="err">' + esc(T.check.errors(errors)) + "</i>" : "") + (errors && warnings ? " " : "") + (warnings ? '<i class="warn">' + esc(T.check.warnings(warnings)) + "</i>" : "") + "</span>";
  }
  var rich = (text) => esc(text).split(REF_OPEN).join('<code class="ck-ref">').split(REF_CLOSE).join("</code>");
  function cardHtml(card3, page) {
    const n = String(++page.cards);
    const error = card3.severity === "error";
    return '<article class="ck-card ' + (error ? "err" : "warn") + '" aria-labelledby="ck-t' + n + '"><div class="ck-top"><span class="ck-sev">' + esc(error ? T.cards.error : T.cards.warning) + '</span><h6 class="ck-title" id="ck-t' + n + '">' + esc(card3.title) + '</h6><button type="button" class="ck-toggle" data-toggle aria-expanded="true" aria-controls="ck-b' + n + '" hidden>' + esc(T.cards.unfold) + '</button><label class="ck-done"><input type="checkbox" data-done> ' + esc(T.cards.done) + "</label></div>" + (card3.where.length ? '<p class="ck-where">' + card3.where.map((w) => "<span>" + rich(w) + "</span>").join('<i aria-hidden="true">›</i>') + "</p>" : "") + '<div class="ck-body" id="ck-b' + n + '"><p class="ck-what">' + rich(card3.what) + "</p>" + (card3.table ? tableHtml(card3.table) : "") + '<div class="ck-boxes"><div class="ck-box blocks"><b>' + esc(T.cards.blocksTitle) + "</b><p>" + rich(card3.blocks) + "</p></div>" + fixesHtml(card3.fixes) + "</div></div></article>";
  }
  function rowHtml(row2) {
    return '<tr><th scope="row">' + rich(row2.label) + (row2.sub === void 0 ? "" : "<small>" + rich(row2.sub) + "</small>") + "</th>" + row2.cells.map(
      (c) => "<td" + (c.odd ? ' class="odd"' : "") + "><b>" + (c.text === "" ? "<em>" + esc(T.cards.blank) + "</em>" : rich(c.text)) + "</b>" + (c.ref === void 0 ? "" : "<small>" + esc(c.ref) + "</small>") + "</td>"
    ).join("") + "</tr>";
  }
  function tableHtml(table2) {
    const head = "<thead><tr>" + table2.head.map((h) => '<th scope="col">' + esc(h) + "</th>").join("") + "</tr></thead>";
    const body = (rows3) => '<div class="ck-tw"><table class="ck-table">' + head + "<tbody>" + rows3.map(rowHtml).join("") + "</tbody></table></div>";
    return body(table2.rows) + (table2.more?.length ? '<details class="ck-more"><summary>' + esc(T.cards.more(table2.more.length)) + "</summary>" + body(table2.more) + "</details>" : "");
  }
  function fixesHtml(fixes) {
    return '<div class="ck-box fix"><b>' + esc(T.cards.fixTitle) + "</b><ol>" + fixes.map(
      (f) => '<li><span class="ck-when">' + rich(f.when) + '</span> <span class="ck-arrow" aria-hidden="true">→</span> ' + rich(f.action) + (f.changes?.length ? '<details class="ck-more"><summary>' + esc(T.cards.changes(f.changes.length)) + '</summary><ul class="ck-changes">' + f.changes.map((c) => "<li>" + rich(c) + "</li>").join("") + "</ul></details>" : "") + "</li>"
    ).join("") + "</ol></div>";
  }

  // plugins/csm-ui/src/v1/shadow.ts
  var UI_STYLE = "csm-ui-v1";
  function mountShadow(host, styles) {
    const doc = host.ownerDocument;
    const shadow = host.attachShadow({ mode: "open" });
    for (const id of [UI_STYLE, ...styles]) {
      const source = doc.getElementById(id);
      if (!source) continue;
      const style = doc.createElement("style");
      style.textContent = source.textContent;
      shadow.append(style);
    }
    const root = host.firstElementChild;
    root.classList.add("csm-ui");
    shadow.append(...Array.from(host.childNodes));
    return root;
  }
  var TOP_MARK = "data-csm-ui-top";
  function mountToTop(root, label2) {
    const doc = root.ownerDocument;
    const html = doc.documentElement;
    if (html.hasAttribute(TOP_MARK)) return null;
    html.setAttribute(TOP_MARK, "");
    const win = doc.defaultView;
    const button = doc.createElement("button");
    button.type = "button";
    button.className = "csm-ui-top";
    button.setAttribute("aria-label", label2);
    button.title = label2;
    button.innerHTML = '<svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false"><path d="M13 7.83V20h-2V7.83l-5.36 5.36-1.42-1.41L12 4l7.78 7.78-1.42 1.41z"/></svg>';
    root.getRootNode().append(button);
    const update = () => {
      button.classList.toggle("on", win.scrollY > win.innerHeight / 2);
    };
    win.addEventListener("scroll", update, { passive: true });
    update();
    button.addEventListener("click", () => {
      let still = false;
      try {
        still = win.matchMedia("(prefers-reduced-motion: reduce)").matches;
      } catch {
      }
      win.scrollTo({ top: 0, behavior: still ? "auto" : "smooth" });
    });
    return button;
  }

  // plugins/csm-standings/src/site/navigate.ts
  function loadPage(win, url2) {
    win.location.assign(url2);
  }

  // plugins/csm-standings/src/site/update.ts
  var CHECK_MS = 15 * 1e3;
  var CHECK_TIMEOUT_MS = 10 * 1e3;
  var LOAD_TIMEOUT_MS = 20 * 1e3;
  var AUTO_MS = 2 * 60 * 1e3;
  var RETRY_MS = 60 * 1e3;
  var STORE_RELOADED = "csm-standings:reloaded-for";
  var MARK = "data-csm-updates";
  function buildsOf(doc) {
    return Array.from(doc.querySelectorAll("[data-build][data-build-url]")).map((el) => ({
      name: el.dataset.buildName ?? "",
      version: el.dataset.build,
      url: el.dataset.buildUrl
    }));
  }
  var watchers = /* @__PURE__ */ new WeakMap();
  function updatesOf(doc) {
    return watchers.get(doc);
  }
  function watchUpdates(root, now = () => Date.now()) {
    const doc = root.ownerDocument;
    const html = doc.documentElement;
    if (html.hasAttribute(MARK)) return void 0;
    const builds = buildsOf(doc);
    if (builds.length === 0) return void 0;
    html.setAttribute(MARK, "");
    const updates = mountUpdates(root, builds, now, doc.location.protocol !== "file:");
    watchers.set(doc, updates);
    return updates;
  }
  function lastReload(win) {
    try {
      const kept = JSON.parse(win.sessionStorage.getItem(STORE_RELOADED) ?? "null");
      return kept !== null && typeof kept.v === "string" && typeof kept.t === "number" ? { v: kept.v, t: kept.t } : null;
    } catch {
      return null;
    }
  }
  function rememberReload(win, versions, at2) {
    try {
      win.sessionStorage.setItem(STORE_RELOADED, JSON.stringify({ v: versions, t: at2 }));
    } catch {
    }
  }
  async function deployedVersion(win, url2, now) {
    const fetch = win.fetch;
    if (fetch === void 0) return null;
    const stop = new AbortController();
    const timer = win.setTimeout(() => {
      stop.abort();
    }, CHECK_TIMEOUT_MS);
    try {
      const res = await fetch.call(win, url2 + (url2.includes("?") ? "&" : "?") + "t=" + String(now()), {
        cache: "no-store",
        signal: stop.signal
      });
      const json = await res.json();
      return res.ok && typeof json?.version === "string" ? json.version : null;
    } catch {
      return null;
    } finally {
      win.clearTimeout(timer);
    }
  }
  function mountUpdates(root, builds, now, checks = true) {
    const doc = root.ownerDocument;
    const win = doc.defaultView;
    const shadow = root.getRootNode();
    const U = T.update;
    const current2 = builds.map((b) => b.version);
    let deployed = null;
    let held = false;
    let shown2 = false;
    let busy = false;
    let loading = false;
    let loadTimer = 0;
    let tick = 0;
    let left = AUTO_MS;
    let last = 0;
    const line = doc.createElement("p");
    line.className = "csm-ui upd-ver";
    line.textContent = U.versions(builds.map((b) => U.version(b.name, b.version)));
    shadow.append(line);
    const live = doc.createElement("div");
    live.className = "upd-live";
    live.setAttribute("role", "status");
    shadow.append(live);
    const banner = doc.createElement("div");
    banner.className = "csm-ui upd";
    banner.hidden = true;
    banner.innerHTML = '<span class="upd-ic" aria-hidden="true">↻</span><span class="upd-tx"><b>' + esc(U.title) + '</b><span class="upd-left"></span></span><button type="button" class="upd-go">' + esc(U.reload) + "</button>";
    shadow.append(banner);
    const leftEl = banner.querySelector(".upd-left");
    const veil = doc.createElement("div");
    veil.className = "csm-ui upd-veil";
    veil.setAttribute("role", "alert");
    veil.hidden = true;
    veil.innerHTML = '<div class="upd-card" tabindex="-1"><span class="upd-spin" aria-hidden="true"></span><b>' + esc(U.loading) + "</b><span>" + esc(U.loadingSub) + "</span></div>";
    shadow.append(veil);
    const draw = () => {
      const on = deployed !== null && !held && !loading;
      const s = Math.max(0, Math.ceil(left / 1e3));
      leftEl.textContent = U.left(U.clock(Math.floor(s / 60), s % 60));
      banner.hidden = !on;
      line.hidden = held;
      if (on !== shown2) live.textContent = on ? U.live(AUTO_MS / 6e4) : "";
      shown2 = on;
      line.style.paddingBottom = on ? String(banner.offsetHeight + 24) + "px" : "";
    };
    const unveil = () => {
      win.clearTimeout(loadTimer);
      loading = false;
      left = AUTO_MS;
      veil.hidden = true;
      root.inert = false;
      draw();
    };
    const count2 = () => {
      const t = now();
      if (doc.visibilityState === "visible" && !held && !loading) left -= t - last;
      last = t;
      draw();
      if (left <= 0) updates.reloadTo(win.location.href);
    };
    const updates = {
      get pending() {
        if (deployed === null) return false;
        const tried = lastReload(win);
        return !(tried !== null && tried.v === deployed && now() - tried.t < RETRY_MS);
      },
      hold(on) {
        held = on;
        draw();
      },
      reloadTo(url2) {
        if (loading) return;
        loading = true;
        if (deployed !== null) rememberReload(win, deployed, now());
        draw();
        veil.hidden = false;
        root.inert = true;
        veil.firstElementChild.focus();
        loadTimer = win.setTimeout(unveil, LOAD_TIMEOUT_MS);
        loadPage(win, url2);
      },
      async check() {
        if (!checks || deployed !== null || busy) return;
        busy = true;
        const found = await Promise.all(builds.map((b) => deployedVersion(win, b.url, now)));
        busy = false;
        if (!found.some((v, i) => v !== null && v !== current2[i])) return;
        deployed = found.map((v, i) => v ?? current2[i]).join("|");
        last = now();
        tick = win.setInterval(count2, 1e3);
        draw();
      },
      stop() {
        win.clearInterval(timer);
        win.clearInterval(tick);
        win.clearTimeout(loadTimer);
      }
    };
    const history = win.history;
    const push = history.pushState.bind(history);
    history.pushState = (data, unused, url2) => {
      if (updates.pending && !held && url2 !== void 0 && url2 !== null) {
        updates.reloadTo(new URL(String(url2), win.location.href).href);
        return;
      }
      push(data, unused, url2);
    };
    win.addEventListener("popstate", (ev) => {
      if (!updates.pending || held) return;
      ev.stopImmediatePropagation();
      updates.reloadTo(win.location.href);
    });
    banner.querySelector(".upd-go").addEventListener("click", () => {
      updates.reloadTo(win.location.href);
    });
    const timer = win.setInterval(() => {
      if (doc.visibilityState === "visible") void updates.check();
    }, CHECK_MS);
    doc.addEventListener("visibilitychange", () => {
      if (doc.visibilityState === "visible") void updates.check();
    });
    win.addEventListener("pageshow", (ev) => {
      if (!ev.persisted) return;
      unveil();
      void updates.check();
    });
    return updates;
  }

  // plugins/csm-standings/src/site/host.ts
  var STANDINGS_STYLE = "csm-standings-css";
  function allowZoom(doc) {
    for (const meta of doc.querySelectorAll('meta[name="viewport"]')) {
      const kept = meta.content.split(",").map((part) => part.trim()).filter((part) => !/^(maximum-scale|user-scalable)\s*=/i.test(part));
      meta.content = kept.join(", ");
    }
  }
  function rootIn(host) {
    const lang = langOf(host.lang);
    useLanguage(lang);
    const root = mountShadow(host, [STANDINGS_STYLE]);
    root.id = host.id;
    root.lang = lang;
    mountToTop(root, T.toTop);
    watchUpdates(root);
    allowZoom(host.ownerDocument);
    return root;
  }

  // plugins/csm-standings/src/site/favorites.ts
  var STORE_FAVS = "csm-standings:favoris";
  function storage(win) {
    try {
      return win.localStorage;
    } catch {
      return null;
    }
  }
  function favsOf(win) {
    try {
      const list3 = JSON.parse(storage(win)?.getItem(STORE_FAVS) ?? "[]");
      return Array.isArray(list3) ? list3.filter((x) => typeof x === "string") : [];
    } catch {
      return [];
    }
  }
  function toggleFav(win, name2) {
    const list3 = favsOf(win);
    try {
      storage(win)?.setItem(
        STORE_FAVS,
        JSON.stringify(list3.includes(name2) ? list3.filter((x) => x !== name2) : [...list3, name2])
      );
    } catch {
    }
  }

  // plugins/csm-standings/src/site/history.ts
  var KEYS = [
    "season",
    "championship",
    "view",
    "class",
    "race",
    "rider",
    "media-kit",
    "event",
    "date",
    "awards",
    "slide"
  ];
  function withParams(href, params) {
    const url2 = new URL(href);
    for (const key of KEYS) url2.searchParams.delete(key);
    url2.hash = "";
    for (const [key, value] of Object.entries(params)) url2.searchParams.set(key, value);
    return url2.toString().replace(/([?&](?:awards|media-kit))=(?=&|$)/, "$1");
  }
  function galaUrl(href, season, palmares) {
    return withParams(href, palmares ? { season, awards: "" } : { season });
  }
  function urlOf(href, model, place2) {
    const params = { season: model.season };
    const ev = place2.event;
    if (place2.person !== null) {
      params.rider = place2.person;
      if (place2.kit === true) params["media-kit"] = "";
    } else if (ev !== null) {
      params.event = ev.name;
      if (model.calendar.filter((e) => e.event === ev.name).length > 1) params.date = ev.start;
      if (ev.cls !== void 0) params.class = ev.cls;
    } else if (place2.palmares) {
      params.awards = "";
      if (place2.slide >= 0) params.slide = String(place2.slide);
    } else if (place2.sel !== "overview") {
      params.championship = place2.sel;
      params.view = "standings";
      if (place2.cls !== null) params.class = place2.cls;
    }
    return withParams(href, params);
  }
  function placeOf(href, model, palmares = "none") {
    const params = new URL(href).searchParams;
    const place2 = {
      sel: "overview",
      cls: null,
      person: null,
      event: null,
      palmares: false,
      slide: -1,
      y: 0,
      depth: 0
    };
    const person = params.get("rider");
    if (person !== null && model.people.has(person)) {
      place2.person = person;
      if (params.has("media-kit") && model.mediaKit.has(person)) place2.kit = true;
      return place2;
    }
    const name2 = params.get("event");
    const start = params.get("date");
    const e = model.calendar.find((x) => x.event === name2 && (start === null || x.start === start));
    if (e) {
      place2.event = { name: e.event, start: e.start };
      const cls2 = params.get("class");
      if (e.codes.some((code2) => model.byCode.get(code2).classes.some((k) => k.name === cls2)))
        place2.event.cls = cls2;
      return place2;
    }
    if (palmares !== "none" && params.has("awards")) {
      place2.palmares = true;
      const slide2 = params.get("slide") ?? "";
      if (palmares === "open" && /^(0|[1-9]\d*)$/.test(slide2)) place2.slide = Number(slide2);
      return place2;
    }
    const code = params.get("championship");
    const champ = code === null ? void 0 : model.byCode.get(code);
    if (!champ) return place2;
    if (params.get("view") === "races") {
      const race = params.get("race");
      const ev = race !== null && champ.events.includes(race) ? race : champ.events.filter((x) => hasResults(champ, x)).pop() ?? champ.events[0];
      const row2 = calFor(model, champ.code, ev);
      if (row2) {
        place2.event = { name: row2.event, start: row2.start };
        return place2;
      }
    }
    place2.sel = champ.code;
    const cls = params.get("class");
    if (champ.classes.some((k) => k.name === cls)) place2.cls = cls;
    return place2;
  }

  // plugins/csm-standings/src/site/views/search.ts
  var LIMIT = 10;
  var LIST_ID = "csm-sres";
  function matches(model, q) {
    const key = fold(q.trim());
    return [...model.people.keys()].filter((name2) => fold(name2).includes(key)).sort(byName);
  }
  function marked(name2, q) {
    const key = fold(q.trim());
    const at2 = fold(name2).indexOf(key);
    return esc(name2.slice(0, at2)) + "<mark>" + esc(name2.slice(at2, at2 + key.length)) + "</mark>" + esc(name2.slice(at2 + key.length));
  }
  function row(model, name2, q, fav, i) {
    const plates = [...new Set(entriesOf(model, name2).map((e) => e.no))].filter((p) => p !== "");
    return '<li role="option" id="' + LIST_ID + "-" + String(i) + '" class="sopt" data-person="' + esc(name2) + '" aria-selected="false"><span class="splates">' + plates.map((p) => '<span class="splate">' + esc(p) + "</span>").join("") + '</span><span class="sname">' + (fav ? '<span class="sfav" aria-label="' + esc(T.favs.added) + '">★</span>' : "") + marked(name2, q) + '</span><span class="sgo" aria-hidden="true">›</span></li>';
  }
  function searchList(model, q, favs) {
    const S = T.search;
    if (!q.trim()) return '<p class="shint">' + esc(S.hint) + "</p>";
    const names = matches(model, q);
    if (!names.length) return '<p class="sempty">' + esc(T.noRiderFound) + "</p>";
    return '<p class="shead" aria-hidden="true"><span>' + esc(S.head) + '</span><span class="n">' + String(names.length) + '</span></p><ul class="slist">' + names.slice(0, LIMIT).map((n, i) => row(model, n, q, favs.includes(n), i)).join("") + "</ul>" + (names.length > LIMIT ? '<p class="smore">' + esc(S.more(names.length - LIMIT)) + "</p>" : "");
  }
  function searchBox(q) {
    return '<div class="search"><input data-input="dq" type="search" role="combobox" aria-autocomplete="list" aria-expanded="false" aria-controls="' + LIST_ID + '" placeholder="' + esc(T.searchPlaceholder) + '" aria-label="' + esc(T.findRider) + '" value="' + esc(q) + '" autocomplete="off" autocorrect="off" spellcheck="false" enterkeyhint="search"><button type="button" data-clear="dq" aria-label="' + esc(T.clearSearch) + '">×</button></div><button type="button" class="scancel" data-scancel="1">' + esc(T.search.cancel) + "</button>";
  }
  function searchDrop() {
    return '<div class="dres" id="' + LIST_ID + '" role="listbox" aria-label="' + esc(T.search.listLabel) + '" hidden></div><p class="vh" aria-live="polite" data-scount></p>';
  }

  // plugins/csm-standings/src/site/rider-search.ts
  var PHONE = "(max-width: 599px)";
  var MIN_PX = 160;
  var GAP_PX = 12;
  var RiderSearch = class {
    constructor(root, model, state, favs) {
      __publicField(this, "root");
      __publicField(this, "model");
      __publicField(this, "state");
      __publicField(this, "win");
      __publicField(this, "favs");
      /** The row ↓ ↑ are on, or -1. */
      __publicField(this, "active", -1);
      /** The phone's panel is open, with its own history entry. */
      __publicField(this, "panel", false);
      this.root = root;
      this.model = model;
      this.state = state;
      this.favs = favs;
      this.win = root.ownerDocument.defaultView;
      const isField = (ev) => ev.target.dataset.input === "dq";
      root.addEventListener("input", (ev) => {
        if (!isField(ev)) return;
        this.state.dq = this.field().value;
        this.show();
      });
      root.addEventListener("focusin", (ev) => {
        if (!isField(ev)) return;
        if (this.phone()) this.openPanel();
        if (this.state.dq || this.panel) this.show();
      });
      root.addEventListener("keydown", (ev) => {
        if (isField(ev)) this.key(ev);
      });
      root.addEventListener("click", (ev) => {
        if (!ev.target.closest(".usearch")) this.hide();
      });
      this.win.addEventListener("popstate", (ev) => {
        if (!this.panel) return;
        ev.stopImmediatePropagation();
        this.closePanel(false);
      });
      const fit2 = () => {
        this.fit();
      };
      this.win.visualViewport?.addEventListener("resize", fit2);
      this.win.visualViewport?.addEventListener("scroll", fit2);
    }
    $(sel) {
      return this.root.querySelector(sel);
    }
    field() {
      return this.$('[data-input="dq"]');
    }
    list() {
      return this.$(".dres");
    }
    phone() {
      try {
        return this.win.matchMedia(PHONE).matches;
      } catch {
        return false;
      }
    }
    /** The page's click on the search's own buttons: « Annuler » (the phone's panel), else ×. */
    click(b) {
      this.state.dq = "";
      this.field().value = "";
      if (b.dataset.scancel) {
        this.closePanel(true);
        return;
      }
      this.field().focus();
      this.show();
    }
    /** Before the page opens the rider picked: the panel goes, its history entry becomes the page's (no extra Back). */
    picked() {
      this.state.dq = "";
      this.setPanel(false);
    }
    show() {
      const list3 = this.list();
      const q = this.state.dq;
      list3.innerHTML = searchList(this.model, q, this.favs());
      const shown2 = this.panel || q.trim() !== "";
      list3.hidden = !shown2;
      this.field().setAttribute("aria-expanded", String(shown2));
      this.move(-1);
      this.$("[data-scount]").textContent = q.trim() ? T.search.count(matches(this.model, q).length) : "";
      this.fit();
    }
    hide() {
      const list3 = this.list();
      if (list3.hidden) return;
      list3.hidden = true;
      this.field().setAttribute("aria-expanded", "false");
      this.move(-1);
      list3.style.maxHeight = "";
    }
    rows() {
      return [...this.root.querySelectorAll(".dres .sopt")];
    }
    move(i) {
      const rows3 = this.rows();
      this.active = i;
      rows3.forEach((r, j) => {
        r.setAttribute("aria-selected", String(j === i));
      });
      if (i < 0) {
        this.field().removeAttribute("aria-activedescendant");
        return;
      }
      this.field().setAttribute("aria-activedescendant", rows3[i].id);
      rows3[i].scrollIntoView({ block: "nearest" });
    }
    key(ev) {
      const rows3 = this.rows();
      const last = rows3.length - 1;
      const hidden = this.list().hidden;
      if (ev.key === "ArrowDown" || ev.key === "ArrowUp") {
        ev.preventDefault();
        if (hidden) {
          if (this.state.dq.trim()) this.show();
        } else if (rows3.length) {
          const down = ev.key === "ArrowDown";
          this.move(down ? this.active >= last ? 0 : this.active + 1 : this.active <= 0 ? last : this.active - 1);
        }
      } else if ((ev.key === "Home" || ev.key === "End") && this.active >= 0) {
        ev.preventDefault();
        this.move(ev.key === "Home" ? 0 : last);
      } else if (ev.key === "Enter") {
        ev.preventDefault();
        const r = this.active >= 0 ? rows3[this.active] : rows3.length === 1 ? rows3[0] : null;
        if (r) r.click();
        else this.field().blur();
      } else if (ev.key === "Escape") {
        ev.preventDefault();
        ev.stopPropagation();
        if (this.panel) this.click(this.$("[data-scancel]"));
        else if (!hidden) this.hide();
        else if (this.state.dq) this.click(this.$("[data-clear]"));
      }
    }
    /** The panel's class on the page's root (redrawn views never touch it), and the page under it still. */
    setPanel(open) {
      this.panel = open;
      this.root.classList.toggle("sopen", open);
      this.root.ownerDocument.documentElement.style.overflow = open ? "hidden" : "";
    }
    openPanel() {
      if (this.panel) return;
      this.setPanel(true);
      this.win.history.pushState(this.win.history.state, "");
    }
    /** Closes the panel; `back`: also its history entry (« Annuler », Escape). */
    closePanel(back) {
      this.setPanel(false);
      this.hide();
      this.field().blur();
      if (back) this.win.history.back();
    }
    /** The panel on the part of the screen the keyboard leaves; the drop-down never runs past it either. */
    fit() {
      const vv = this.win.visualViewport;
      const h = vv ? vv.height : this.win.innerHeight;
      const top2 = vv ? vv.offsetTop : 0;
      this.root.style.setProperty("--svh", String(h) + "px");
      this.root.style.setProperty("--svt", String(top2) + "px");
      const list3 = this.list();
      if (this.panel || list3.hidden) {
        list3.style.maxHeight = "";
        return;
      }
      const room = top2 + h - list3.getBoundingClientRect().top - GAP_PX;
      list3.style.maxHeight = String(Math.max(MIN_PX, room)) + "px";
    }
  };

  // plugins/csm-standings/src/site/show.ts
  var WIDTH2 = 1600;
  var HEIGHT = 900;
  var COUNT_MS = 1200;
  var TOC_WIDE = 700;
  var ZOOMED = 1.01;
  var ignore = () => void 0;
  var Show = class {
    constructor(root, model, palmares) {
      __publicField(this, "slides");
      /** The table of contents is shown instead of the current slide. */
      __publicField(this, "toc", false);
      __publicField(this, "root");
      __publicField(this, "model");
      __publicField(this, "win");
      __publicField(this, "el");
      /** « Auto »: a podium's places come in by themselves; else one click each (the default, every time the show opens). */
      __publicField(this, "auto", false);
      /** The slide on screen. */
      __publicField(this, "i", 0);
      /** Its places already in (a podium). */
      __publicField(this, "shown", 0);
      /** In « Auto », when the places still to come started coming in (performance.now()). */
      __publicField(this, "t0", 0);
      /** When they are all in; 0 = nothing comes in by itself. */
      __publicField(this, "revealEnd", 0);
      this.root = root;
      this.model = model;
      this.win = root.ownerDocument.defaultView;
      this.slides = slidesOf(model, palmares);
      this.el = root.ownerDocument.createElement("div");
      this.el.className = "gl-show";
      this.el.setAttribute("popover", "manual");
      this.win.addEventListener("resize", () => {
        this.fit();
      });
      root.ownerDocument.addEventListener("fullscreenchange", () => {
        this.fit();
      });
      this.win.visualViewport?.addEventListener("resize", () => {
        this.el.classList.toggle("zoomed", this.zoomed());
      });
      const win = this.win;
      for (const c of model.champs) if (c.logo) new win.Image().src = c.logo;
      for (const s of model.sponsors) if (s.image) new win.Image().src = s.image.url;
    }
    /** The slide `n` kept within the show. */
    clamp(n) {
      return Math.max(0, Math.min(this.slides.length - 1, n));
    }
    /** In « Auto », the slide on screen is still coming in (a class's podium). */
    revealing() {
      return this.revealEnd > 0 && this.win.performance.now() < this.revealEnd;
    }
    /**
     * « Next » within the slide on screen: one more place (one click per
     * place), or the whole podium at once while it comes in (« Auto »).
     * False when the slide is all in: the next slide's turn.
     */
    step() {
      const s = this.slides[this.i];
      if (this.auto) {
        if (!this.revealing()) return false;
        this.shown = s.places;
        this.paint();
        return true;
      }
      if (this.shown >= s.places) return false;
      revealPlace(this.el, this.shown, s.places, s.color);
      this.shown++;
      if (this.shown === s.places) this.countUp();
      return true;
    }
    /** Switches between one click per place and « Auto », at once: the places already in stay, the others come in the new way. */
    setAuto(on) {
      const s = this.slides[this.i];
      const before = this.shown;
      if (this.auto && this.revealEnd)
        this.shown = Math.min(
          s.places,
          this.shown + Math.floor((this.win.performance.now() - this.t0) / (REVEAL_STEP * 1e3))
        );
      if (on && this.still()) this.shown = s.places;
      this.auto = on;
      if (this.shown < s.places || this.shown !== before) this.paint();
      else this.el.querySelector(".gl-mode").outerHTML = modeButton(on);
    }
    /** The slide on screen again, after the table of contents: its places kept (« Auto »: whole, as ←). */
    redraw() {
      if (this.auto) this.shown = this.slides[this.i].places;
      this.paint();
    }
    /** Shows a slide; `entering`: moving forward, so it plays its entrance. */
    draw(i, entering) {
      if (!this.el.isConnected) {
        this.root.append(this.el);
        this.onTop();
        this.el.classList.toggle("zoomed", this.zoomed());
      }
      this.toTop(true);
      if (this.toc) {
        const wide = this.el.clientWidth >= TOC_WIDE;
        this.el.innerHTML = tocHtml(this.model, this.slides, i, wide ? "all" : "current");
        this.el.querySelector("[aria-current]").scrollIntoView({ block: "center" });
        return;
      }
      const still = this.still();
      this.i = i;
      this.shown = entering && !(this.auto && still) ? 0 : this.slides[i].places;
      this.paint();
      if (entering && !still) this.countUp();
    }
    /** The slide on screen, its places already in, the others coming in by themselves (« Auto ») or waiting. */
    paint() {
      const s = this.slides[this.i];
      this.t0 = this.win.performance.now();
      const ms = this.auto ? revealMs(s.places - this.shown) : 0;
      this.revealEnd = ms ? this.t0 + ms : 0;
      this.el.style.setProperty("--c", s.color);
      this.el.innerHTML = stageHtml(this.slides, this.i, this.shown, this.auto);
      this.fit();
      fitPromotionNames(this.el, this.win);
    }
    /** Closes the show (and full screen). */
    hide() {
      const doc = this.root.ownerDocument;
      try {
        if (doc.fullscreenElement) doc.exitFullscreen().catch(ignore);
      } catch {
      }
      this.toc = false;
      this.auto = false;
      this.revealEnd = 0;
      this.el.remove();
      this.toTop(false);
    }
    /**
     * In the browser's top layer: above the theme's menu, whatever its z-index (Safari on an iPhone has no
     * full screen). Taking it out of the page (hide) takes it out of the top layer. Without the Popover
     * API: the layer's own z-index.
     */
    onTop() {
      try {
        this.el.showPopover();
      } catch {
      }
    }
    /** The visitor has pinched the screen bigger: one finger moves the view, it doesn't swipe. */
    zoomed() {
      return (this.win.visualViewport?.scale ?? 1) > ZOOMED;
    }
    /** The page's back-to-top button (CSM UI), when it is beside this root: out of reach behind the show. */
    toTop(hidden) {
      this.root.getRootNode().querySelector(".csm-ui-top")?.toggleAttribute("hidden", hidden);
    }
    /** Full screen on, or off when it is on. Refused or not there: the show stays a layer over the window. */
    fullscreen() {
      const doc = this.root.ownerDocument;
      try {
        if (doc.fullscreenElement) doc.exitFullscreen().catch(ignore);
        else this.el.requestFullscreen().catch(ignore);
      } catch {
      }
    }
    /** The canvas scaled to the screen, centred. */
    fit() {
      const canvas = this.el.querySelector(".gl-canvas");
      if (!canvas) return;
      const k = Math.min(this.el.clientWidth / WIDTH2, this.el.clientHeight / HEIGHT);
      canvas.style.transform = "translate(-50%,-50%) scale(" + String(k) + ")";
    }
    /** The visitor asked for less motion. */
    still() {
      try {
        return this.win.matchMedia("(prefers-reduced-motion: reduce)").matches;
      } catch {
        return true;
      }
    }
    /** The big figures count up from 0. */
    countUp() {
      for (const el of this.el.querySelectorAll("[data-count]")) {
        const to = Number(el.dataset.count);
        const t0 = this.win.performance.now();
        const tick = (t) => {
          const x = Math.min(1, (t - t0) / COUNT_MS);
          el.textContent = String(Math.round(to * (1 - Math.pow(1 - x, 3))));
          if (x < 1) this.win.requestAnimationFrame(tick);
        };
        this.win.requestAnimationFrame(tick);
      }
    }
  };

  // plugins/csm-standings/src/site/season-menu.ts
  function listenSeason(root) {
    const win = root.ownerDocument.defaultView;
    const select = root.querySelector("[data-season]");
    const button = root.querySelector(".ybtn");
    const menu = root.querySelector(".ymenu");
    const options = [...menu.querySelectorAll("[data-yopt]")];
    select.addEventListener("change", () => {
      const url2 = new URL(win.location.href);
      for (const key of KEYS) url2.searchParams.delete(key);
      url2.searchParams.set("season", select.value);
      loadPage(win, url2.toString());
    });
    const open = () => {
      menu.hidden = false;
      button.setAttribute("aria-expanded", "true");
      menu.querySelector('[aria-selected="true"]').focus();
    };
    const close = (focus) => {
      menu.hidden = true;
      button.setAttribute("aria-expanded", "false");
      if (focus) button.focus();
    };
    root.addEventListener("click", (ev) => {
      const target = ev.target;
      const opt = target.closest("[data-yopt]");
      if (opt) {
        close(true);
        if (select.value !== opt.dataset.yopt) {
          select.value = opt.dataset.yopt;
          select.dispatchEvent(new win.Event("change"));
        }
      } else if (target.closest(".ybtn")) {
        if (menu.hidden) open();
        else close(false);
      } else if (!menu.hidden && !target.closest(".ymenu")) close(false);
    });
    button.addEventListener("keydown", (ev) => {
      if (ev.key === "ArrowDown" || ev.key === "ArrowUp") {
        ev.preventDefault();
        open();
      }
    });
    menu.addEventListener("keydown", (ev) => {
      const i = options.indexOf(ev.target);
      const to = ev.key === "ArrowDown" ? Math.min(i + 1, options.length - 1) : ev.key === "ArrowUp" ? Math.max(i - 1, 0) : ev.key === "Home" ? 0 : ev.key === "End" ? options.length - 1 : -1;
      if (to >= 0) {
        ev.preventDefault();
        options[to].focus();
      } else if (ev.key === "Escape") {
        ev.preventDefault();
        ev.stopPropagation();
        close(true);
      }
    });
    menu.addEventListener("focusout", (ev) => {
      const next = ev.relatedTarget;
      if (next && !next.closest(".ypick")) close(false);
    });
  }

  // plugins/csm-standings/src/dossier/province.ts
  var PROVINCE = /,\s*(QC|ON|NB|NS|PE|NL|MB|SK|AB|BC|YT|NT|NU)\b/;
  function provinceOf(ev) {
    const m = PROVINCE.exec(ev.place?.address ?? "");
    return m ? m[1] : null;
  }

  // plugins/csm-standings/src/dossier/data.ts
  function finalsOf2(model, all) {
    const finals = /* @__PURE__ */ new Map();
    for (const ev of model.calendar)
      for (const e of all) {
        const c = model.byCode.get(e.code);
        if (!ev.codes.includes(e.code) || !c.events.includes(ev.event)) continue;
        const r = raceResult(model, e, ev.event);
        if (r.kind !== "pts" && r.kind !== "bad") continue;
        const key = String(ev.row) + "\n" + e.cls;
        const known = finals.get(key);
        if (known) {
          known.codes.push(e.code);
          continue;
        }
        const text = e.row[ev.event];
        finals.set(key, {
          ev,
          cls: e.cls,
          codes: [e.code],
          place: r.kind === "pts" ? r.place : null,
          text,
          started: r.kind === "pts" || text === "DNF"
        });
      }
    return [...finals.values()];
  }
  function standingOf(model, e) {
    const c = model.byCode.get(e.code);
    return {
      code: c.code,
      champ: c.name,
      color: c.color,
      logo: c.logo,
      cls: e.cls,
      plate: e.no,
      rank: e.rank,
      of: e.of,
      pts: e.pts,
      final: c.done === c.total
    };
  }
  function markOf(c) {
    if (!c.parts.length) return null;
    const p = c.parts.find((q) => q.r.kind !== "future") ?? c.parts[0];
    return {
      text: p.e.row[c.ev.event],
      place: p.r.kind === "pts" ? p.r.place : null,
      kind: p.r.kind,
      jk: c.parts.some((q) => q.jk)
    };
  }
  function gridOf(model, all) {
    const { rows: rows3, cells } = gridCells(model, all);
    return {
      races: riderRaces(model, all).map((ev) => ({
        event: ev.event,
        short: shortEvent(ev.event),
        days: raceDaysText(ev)
      })),
      rows: rows3.map((row2, i) => ({
        cls: row2.cls,
        codes: [...new Set(row2.entries.map((e) => e.code))],
        cells: cells[i].map(markOf)
      }))
    };
  }
  function honoursOf(model, name2) {
    const named = palmaresOf(model).facts.filter(
      (f) => f.who.includes(name2) || (f.list ?? []).some((x) => x.name === name2)
    );
    return named.map((f) => ({
      title: f.title,
      value: f.challenge ? "" : f.value,
      unit: f.challenge ? "" : f.unit,
      detail: f.detail,
      explanation: f.explain.text,
      trophy: f.trophy === true
    }));
  }
  function riderSeason(model, name2, withHonours = true) {
    if (!model.people.has(name2)) return null;
    const all = entriesOf(model, name2);
    const finals = finalsOf2(model, all);
    const started = finals.filter((f) => f.started);
    const events = model.calendar.filter((ev) => started.some((f) => f.ev === ev));
    const plateUse = /* @__PURE__ */ new Map();
    for (const e of all) plateUse.set(e.no, (plateUse.get(e.no) ?? 0) + 1);
    const stretches = stretchesOf(events);
    const kms = stretches.map((s) => kmOf(model.distances, s));
    const standings = all.map((e) => standingOf(model, e));
    return {
      name: name2,
      season: model.season,
      plates: [...plateUse].sort((a, b) => b[1] - a[1]).map(([p]) => p),
      standings,
      grid: gridOf(model, all),
      finals,
      starts: started.length,
      wins: started.filter((f) => f.place === 1).length,
      podiums: started.filter((f) => f.place !== null && f.place <= 3).length,
      titles: standings.filter((s) => s.rank === 1 && s.final).length,
      leads: standings.filter((s) => s.rank === 1 && !s.final).length,
      classes: [...new Set(all.map((e) => e.cls))],
      champs: [...new Set(all.map((e) => e.code))],
      events,
      provinces: [...new Set(events.map(provinceOf).filter((p) => p !== null))],
      km: kms.every((k) => k !== null) ? kms.reduce((a, b) => a + b, 0) : null,
      honours: withHonours ? honoursOf(model, name2) : [],
      promotions: (model.promotions ?? []).filter((p) => p.name === name2).map((p) => ({ discipline: p.discipline, from: p.from, to: p.to })),
      upcoming: model.calendar.filter((ev) => ev.start > model.today)
    };
  }

  // plugins/csm-standings/src/media-kit/profile.ts
  var KIT_LANGUAGES = ["fr", "en"];
  var FRAMES = {
    cover: { w: 7.3, h: 5.65 },
    portrait: { w: 2.7, h: 5.6 },
    profileLeft: { w: 8.5 / 3, h: 2.3 },
    profileCenter: { w: 8.5 / 3, h: 2.3 },
    profileRight: { w: 8.5 / 3, h: 2.3 },
    offer: { w: 8.5, h: 3 }
  };
  var ON_PAGE = {
    cover: { page: 1, x: 0.6, y: 0.6 },
    portrait: { page: 2, x: 0.6, y: 0.55 },
    profileLeft: { page: 2, x: 0, y: 8.15 },
    profileCenter: { page: 2, x: 8.5 / 3, y: 8.15 },
    profileRight: { page: 2, x: 2 * 8.5 / 3, y: 8.15 },
    offer: { page: 6, x: 0, y: 7.45 }
  };
  var titleOf2 = (name2) => name2.toLowerCase().replace(/(^|[\s-])(\p{L})/gu, (_, sep, c) => sep + c.toUpperCase());

  // plugins/csm-standings/src/site/media-kit.ts
  function kitCard(model, name2) {
    if (!model.mediaKit.has(name2)) return "";
    const K = T.mediaKit;
    return '<section class="mk-card"><div><p class="eyebrow">' + esc(K.cardTitle) + "</p><p>" + esc(K.cardText) + '</p></div><button type="button" class="mk-main" data-kit="1">' + esc(K.button) + " ›</button></section>";
  }
  var KIT_CODE_ITERATIONS = 2e4;
  async function kitCodeDigest(code, season) {
    const utf8 = new TextEncoder();
    const subtle = globalThis.crypto.subtle;
    const key = await subtle.importKey("raw", utf8.encode(code), "PBKDF2", false, ["deriveBits"]);
    const bits = await subtle.deriveBits(
      { name: "PBKDF2", hash: "SHA-256", salt: utf8.encode("csm-media-kit\n" + season), iterations: KIT_CODE_ITERATIONS },
      key,
      256
    );
    return [...new Uint8Array(bits)].map((b) => b.toString(16).padStart(2, "0")).join("");
  }
  function kitGate(season, wrong) {
    const K = T.mediaKit;
    return '<div class="mk-view mk-gate"><button type="button" class="back" data-back="1">' + esc(K.back) + '</button><section class="mk-gate-box"><p class="eyebrow">' + esc(K.eyebrow(season)) + "</p><h2>" + esc(K.codeTitle) + "</h2><p>" + esc(K.codeText) + '</p><form class="mk-code" novalidate><label>' + esc(K.codeLabel) + ' <input type="password" name="code" required autocomplete="current-password" autocapitalize="none" spellcheck="false"></label><button type="submit" class="mk-main">' + esc(K.codeOpen) + "</button></form>" + (wrong ? '<p class="mk-wrong" role="alert">' + esc(K.codeWrong) + "</p>" : "") + '<p class="mk-lost">' + esc(K.codeLost) + "</p></section></div>";
  }
  var disciplineOf = (cls) => cls.replace(/\s*\d+$/, "");
  function startedOf(model, name2) {
    const all = entriesOf(model, name2);
    const finals = /* @__PURE__ */ new Map();
    const weekends = /* @__PURE__ */ new Set();
    for (const ev of model.calendar)
      for (const e of all) {
        const c = model.byCode.get(e.code);
        if (!ev.codes.includes(e.code) || !c.events.includes(ev.event)) continue;
        const r = raceResult(model, e, ev.event);
        if (r.kind !== "pts" && e.row[ev.event] !== "DNF") continue;
        const key = String(ev.row) + "\n" + e.cls;
        if (!finals.has(key)) finals.set(key, r.kind === "pts" ? r.place : null);
        weekends.add(ev);
      }
    return { places: [...finals.values()], weekends: weekends.size };
  }
  function finalsOf3(model, name2) {
    const { places, weekends } = startedOf(model, name2);
    return { finals: places.length, weekends };
  }
  function factsOf(model, name2) {
    const all = entriesOf(model, name2);
    const { places } = startedOf(model, name2);
    const over = (code) => {
      const c = model.byCode.get(code);
      return c.done === c.total;
    };
    const order = byOrder2(model);
    const best2 = all.slice().sort((a, b) => a.rank - b.rank || order(a, b))[0];
    return {
      name: titleOf2(name2),
      season: model.season,
      starts: places.length,
      wins: places.filter((p) => p === 1).length,
      podiums: places.filter((p) => p !== null && p <= 3).length,
      titles: all.filter((e) => e.rank === 1 && over(e.code)).length,
      classes: [...new Set(all.map((e) => e.cls))],
      champs: [...new Set(all.map((e) => e.code))],
      best: places.length ? { cls: best2.cls, code: best2.code, rank: best2.rank, final: over(best2.code) } : null
    };
  }
  function plateOf(all) {
    const use = /* @__PURE__ */ new Map();
    for (const e of all) use.set(e.no, (use.get(e.no) ?? 0) + 1);
    return [...use].sort((x, y) => y[1] - x[1]).map(([p]) => p)[0] ?? "";
  }
  function dossierSeason(payload, season, name2, lang) {
    const was = siteLanguages().lang;
    useLanguage(lang);
    try {
      const wb = workbookOf(payload, siteLanguages());
      return riderSeason(modelOf(seasonOf(wb, payload, season), payload.today), name2);
    } finally {
      useLanguage(was);
    }
  }
  function kitContext(model, name2, el, assets, lang, payload, onBack) {
    const all = entriesOf(model, name2);
    const kitLang = KIT_LANGUAGES.includes(lang) ? lang : "en";
    const seasons = /* @__PURE__ */ new Map();
    return {
      el,
      win: el.ownerDocument.defaultView,
      rider: name2,
      season: model.season,
      lang: kitLang,
      codes: [...new Set(all.map((e) => disciplineOf(e.cls)))],
      ...finalsOf3(model, name2),
      plate: plateOf(all),
      facts: factsOf(model, name2),
      T: T.mediaKit,
      kits: { fr: LANGUAGES.fr.mediaKit.kit, en: LANGUAGES.en.mediaKit.kit },
      sample: assets.folder + "example-" + kitLang + ".pdf?ver=" + assets.ver,
      onBack,
      dossier: {
        season: (l) => {
          if (!seasons.has(l)) seasons.set(l, dossierSeason(payload, model.season, name2, l));
          return seasons.get(l);
        },
        S: { fr: LANGUAGES.fr, en: LANGUAGES.en },
        edition: model.rules.rulebook,
        today: payload.today,
        asOf: T.dateRange({ start: payload.today, end: payload.today }),
        fonts: el.ownerDocument.getElementById("csm-ui-v1-fonts")?.textContent ?? ""
      }
    };
  }
  var KitLoader = class {
    constructor(win, script) {
      __publicField(this, "win");
      __publicField(this, "script");
      __publicField(this, "loading", null);
      this.win = win;
      this.script = script;
    }
    load() {
      const ready = this.win.csmMediaKit;
      if (ready) return Promise.resolve(ready);
      this.loading ?? (this.loading = new Promise((ok, ko) => {
        const s = this.win.document.createElement("script");
        s.src = this.script;
        s.async = true;
        s.addEventListener("load", () => {
          const kit = this.win.csmMediaKit;
          if (kit) ok(kit);
          else ko(new Error("csmMediaKit"));
        });
        s.addEventListener("error", () => {
          ko(new Error("load"));
        });
        this.win.document.head.append(s);
      }).catch((err) => {
        this.loading = null;
        throw err;
      }));
      return this.loading;
    }
    /** « Chargement… », then the form (`still()`: the view is still this one), or the error. */
    open(ctx, still) {
      const K = T.mediaKit;
      const back = '<button type="button" class="back" data-back="1">' + esc(K.back) + "</button>";
      ctx.el.innerHTML = '<div class="mk-view">' + back + '<p class="mk-wait" role="status">' + esc(K.loading) + "</p></div>";
      return this.load().then(
        (kit) => {
          if (still()) kit.mount(ctx);
        },
        () => {
          if (still())
            ctx.el.innerHTML = '<div class="mk-view">' + back + '<p class="mk-wait err" role="alert">' + esc(K.loadError) + "</p></div>";
        }
      );
    }
  };

  // plugins/csm-standings/src/site/state.ts
  function initialState() {
    return {
      sel: "overview",
      cls: {},
      filter: {},
      sort: null,
      dq: "",
      favOpen: false,
      person: null,
      kit: false,
      event: null,
      palmares: false,
      slide: -1,
      depth: 0
    };
  }

  // plugins/csm-standings/src/site/views/championship.ts
  function championship(model, c, state) {
    const banner = champBanner(model, c);
    if (c.blocked) return banner + notice();
    const k = openClass(c, state);
    const body = !c.classes.length ? notice() : k ? classPage(model, c, k, state) : classCards(model, c, state);
    return banner + calendarOf(
      model,
      model.calendar.filter((e) => e.codes.includes(c.code)),
      T.calendarTitle(c.code + " " + model.season),
      false
    ) + '<div class="mode-body">' + body + "</div>";
  }

  // plugins/csm-standings/src/site/views/header.ts
  function favButton(name2, on) {
    return '<button type="button" class="follow" data-fav="' + esc(name2) + '" aria-pressed="' + String(on) + '" title="' + esc(T.favs.hint) + '">' + (on ? "★ " + esc(T.favs.added) : "☆ " + esc(T.favs.add)) + "</button>";
  }
  function favSummary(model, name2) {
    const list3 = entriesOf(model, name2);
    return list3.slice(0, 3).map((x) => x.code + " " + x.cls + " · " + ordText(x.rank)).join(" — ") + (list3.length > 3 ? " — …" : "");
  }
  function favHeader(model, favs, open) {
    const list3 = favs.filter((n) => model.people.has(n)).sort(byName);
    const F = T.favs;
    const items = list3.length ? '<ul class="favlist">' + list3.map(
      (n) => '<li><button type="button" class="fgo" data-person="' + esc(n) + '"><b>' + esc(n) + "</b><span>" + esc(favSummary(model, n)) + '</span></button><button type="button" class="frm" data-fav="' + esc(n) + '" aria-label="' + esc(F.remove(n)) + '">×</button></li>'
    ).join("") + "</ul>" : '<p class="favempty">' + F.empty("<b>☆ " + esc(F.add) + "</b>") + "</p>";
    return '<div class="favwrap"><button type="button" class="favbtn' + (list3.length ? " on" : "") + '" data-favs="1" aria-expanded="' + String(open) + '" aria-label="' + esc(F.buttonAria(list3.length)) + '"><span class="st" aria-hidden="true">' + (list3.length ? "★" : "☆") + '</span><span class="lb">' + esc(F.button) + "</span>" + (list3.length ? '<span class="n">' + String(list3.length) + "</span>" : "") + '</button><div class="favpanel"' + (open ? "" : " hidden") + '><p class="fh">' + esc(F.title(list3.length)) + "</p>" + items + '<p class="fnote">' + esc(F.device) + "</p></div></div>";
  }
  function headerTools(model, dq, favs, open) {
    return '<div class="usearch"><div class="srow">' + favHeader(model, favs, open) + searchBox(dq) + "</div>" + searchDrop() + "</div>";
  }

  // plugins/csm-standings/src/site/views/rider-page.ts
  var LINK_ICON = '<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><path d="M6.5 9.5l3-3M7 4.5l1.2-1.2a2.5 2.5 0 013.5 3.5L10.5 8M9 11.5l-1.2 1.2a2.5 2.5 0 01-3.5-3.5L5.5 8" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>';
  function riderStats(model, all) {
    let starts = 0;
    let wins = 0;
    let podiums = 0;
    for (const race of riderRaces(model, all)) {
      const hits = all.filter(
        (e) => race.codes.includes(e.code) && model.byCode.get(e.code).events.includes(race.event)
      );
      const results2 = hits.map((e) => raceResult(model, e, race.event));
      if (results2.some((r) => r.kind === "pts" || r.kind === "bad")) starts++;
      const best2 = Math.min(...results2.map((r) => r.kind === "pts" ? r.place ?? 99 : 99));
      if (best2 === 1) wins++;
      if (best2 <= 3) podiums++;
    }
    return { starts, wins, podiums };
  }
  function card2(model, e) {
    const riders = e.klass.riders;
    const c = model.byCode.get(e.code);
    const gap = e.rank === 1 ? riders.length > 1 ? e.pts === totalOf(riders[1]) ? T.leaderTied(tieReason(c, e.klass)) : T.leaderBy(e.pts - totalOf(riders[1])) : T.leader : T.behindFirst(totalOf(riders[0]) - e.pts) + (e.rank > 2 ? T.behindAhead(totalOf(riders[e.rank - 2]) - e.pts, e.rank - 1) : "");
    const d = dropped(e.row);
    return '<article class="ecard"><span class="lab"><b>' + esc(e.cls) + "</b> · " + T.plate + " " + esc(e.no) + '</span><div class="big"><span class="pos ' + medal(e.rank) + '">' + ord(e.rank) + '</span><span class="of">' + esc(T.outOf(e.of)) + '</span></div><div class="line">' + pts(e.pts) + "<span>" + esc(gap) + "</span></div>" + (d ? (
      // What the Joker drops explains the rider's total; the rule itself is the rulebook's (a link, never a copy).
      '<p class="ejoker">' + esc(T.jokerHelp.riderLine(d.event, d.value, d.pts)) + " · " + esc(T.jokerHelp.grossLine(e.row[COLUMN_TOTAL], d.pts, e.pts)) + " · " + ruleLink(c, T.jokerHelp.article) + "</p>"
    ) : "") + objectiveLine(objective(model.byCode.get(e.code), e.klass, e.row[COLUMN_NAME])) + '<button type="button" class="go" data-go="' + esc(e.code) + '" data-cls="' + esc(e.cls) + '">' + esc(T.classLink(e.cls)) + "</button></article>";
  }
  function nextRace(model, all) {
    const codes = new Set(all.map((x) => x.code));
    const e = model.calendar.find((r) => r.codes.some((code) => codes.has(code)) && stateOf(r, model.today) !== "past");
    if (!e) return "";
    const live = stateOf(e, model.today) === "live";
    return '<button type="button" class="pnext"' + eventAttrs(e) + '><span class="k">' + esc(live ? T.live : T.nextRace) + "</span><b>" + esc(e.event) + "</b><span>" + dateRange3(e) + atPlace(e, false) + (live ? "" : " · " + esc(T.inDays(daysUntil(e.start, model.today)))) + '</span><span class="chev" aria-hidden="true">›</span></button>';
  }
  function riderPage(model, name2, fav) {
    const all = entriesOf(model, name2);
    const groups = groupsOf(model, all, null);
    const plates = [...new Set(all.map((e) => e.no))];
    const { starts, wins, podiums } = riderStats(model, all);
    const order = byOrder2(model);
    const best2 = all.slice().sort((a, b) => a.rank - b.rank || order(a, b))[0];
    const color = (code) => esc(model.byCode.get(code).color);
    const grid = seasonGrid(model, all);
    const html = '<button type="button" class="back" data-back="1">' + esc(T.back) + '</button><div class="phero"><div><p class="eyebrow">' + esc(T.riderEyebrow(model.season)) + "</p><h2>" + esc(name2) + '</h2><div class="meta" style="font:600 13px var(--csm-f-label);color:var(--csm-muted)">' + esc(T.championships(groups.length) + " · " + T.categories(all.length)) + "</div>" + nextRace(model, all) + '</div><div class="hero-r"><div class="plates">' + plates.map((p) => '<span class="plate"><small>' + T.plateCaps + "</small>" + esc(p) + "</span>").join("") + '</div><button type="button" class="copy" data-copy="1">' + LINK_ICON + "<span>" + esc(T.copyLink) + "</span></button>" + favButton(name2, fav) + '</div></div><div class="pstats"><div><b>' + String(starts) + "</b><span>" + esc(T.statStarts) + "</span></div><div><b>" + String(wins) + "</b><span>" + esc(T.statWins) + "</span></div><div><b>" + String(podiums) + "</b><span>" + esc(T.statPodiums) + "</span></div><div><b>" + ord(best2.rank) + "</b><span>" + esc(T.statBest(best2.code, best2.cls)) + "</span></div></div>" + kitCard(model, name2) + sectionHead(T.standingsTitle, T.standingsAside) + '<div class="cgroups">' + groups.map(([code, list3]) => {
      const c = model.byCode.get(code);
      return '<section class="cgroup" style="--c:' + color(code) + '"><header class="cg-h"><button type="button" class="lgo" data-sel="' + esc(code) + '" aria-label="' + esc(T.standingsTitle + T.colon + c.name) + '"></button>' + logo(c) + '<div><div class="t">' + esc(c.name) + ' <span class="lgo-i" aria-hidden="true">›</span></div><div class="s">' + champStatus(c) + '</div></div></header><div class="ecards">' + list3.map((e) => card2(model, e)).join("") + "</div></section>";
    }).join("") + "</div>" + sectionHead(T.gridTitle, T.gridAside, legendButton(GRID_LEGEND)) + grid.html;
    return { html, cells: grid.cells };
  }

  // plugins/csm-standings/src/site/page.ts
  function mountClassements(root, payload) {
    const workbook = workbookOf(payload, siteLanguages());
    const season = payload.season ?? "";
    const top2 = header(root.id, workbook.seasons, season);
    const data = workbook.blocked || !workbook.seasons.includes(season) ? null : seasonOf(workbook, payload, season);
    if (data === null || data.championships.length === 0) {
      root.innerHTML = '<main class="wrap">' + top2 + notice() + "</main>";
      listenSeason(root);
      return;
    }
    const model = modelOf(data, payload.today);
    if (!payload.mediaKitAssets) model.mediaKit = /* @__PURE__ */ new Map();
    new ClassementsPage(root, model, top2, payload).start();
  }
  var NEXT_KEYS = ["ArrowRight", " ", "PageDown", "Enter", "ArrowDown"];
  var PREV_KEYS = ["ArrowLeft", "PageUp", "Backspace", "ArrowUp"];
  var SWIPE_PX = 50;
  var SWIPE_CLICK_MS = 400;
  var ClassementsPage = class {
    constructor(root, model, top2, payload) {
      __publicField(this, "root");
      __publicField(this, "model");
      __publicField(this, "win");
      __publicField(this, "state", initialState());
      __publicField(this, "cells", []);
      __publicField(this, "barEl");
      __publicField(this, "viewEl");
      /** The header's favourites and rider search. */
      __publicField(this, "toolsEl");
      /** The rider search's behaviour (its list, its keys, the phone's panel). */
      __publicField(this, "search");
      /** The Palmarès is open to this visitor (public, or the code: the PHP decides). */
      __publicField(this, "galaOpen");
      /** Not public yet and no code: ?awards shows the form asking for it. */
      __publicField(this, "galaGate");
      /** The form was sent with a wrong code. */
      __publicField(this, "galaWrong");
      /** « Aperçu privé… » and « Quitter l'aperçu privé » while only the code opens it, else "". */
      __publicField(this, "quit");
      /** The Palmarès and its show, computed the first time they are needed. */
      __publicField(this, "gala", null);
      /** The media kit form's script and sample (null: no MEDIA KIT tab). */
      __publicField(this, "kit");
      /** The riders whose code was typed right while the page is open: their form opens without asking again. */
      __publicField(this, "kitOpen", /* @__PURE__ */ new Set());
      /** The media kit form's own Back (its PDF preview open): asked first, true when the Back was the form's. */
      __publicField(this, "kitBack", null);
      /** When a swipe last moved the show (performance.now()): the click that may follow is not a « next ». */
      __publicField(this, "swipedAt", -Infinity);
      /** Gives the focus back to the button of a help opened without showModal. */
      __publicField(this, "helpBack", null);
      this.root = root;
      this.model = model;
      this.win = root.ownerDocument.defaultView;
      const access = payload.galaAccess ?? "none";
      this.galaOpen = access === "public" || access === "unlocked";
      this.galaGate = access === "locked";
      this.galaWrong = payload.galaWrong === true;
      const assets = payload.mediaKitAssets;
      this.kit = assets ? { loader: new KitLoader(this.win, assets.script), assets, payload } : null;
      const when = galaWhen(payload.galaPublic ?? "");
      this.quit = access !== "unlocked" ? "" : quitForm(
        when ? T.gala.privateNote(when) : T.gala.privateNoDate,
        galaUrl(this.win.location.href, model.season, false)
      );
      root.innerHTML = '<main class="wrap">' + top2 + '<nav class="bar" aria-label="' + esc(T.barLabel) + '"></nav><div class="view"></div></main>';
      this.barEl = root.querySelector(".bar");
      this.viewEl = root.querySelector(".view");
      this.toolsEl = root.querySelector(".gslot");
      this.search = new RiderSearch(root, model, this.state, () => favsOf(this.win));
    }
    start() {
      this.apply(placeOf(this.win.location.href, this.model, this.galaOpen ? "open" : this.galaGate ? "gate" : "none"));
      this.listen();
      this.render();
      this.$(".rcard.hit")?.scrollIntoView({ block: "start" });
      this.writeHistory(false);
    }
    $(sel) {
      return this.root.querySelector(sel);
    }
    /** The Palmarès and its show. */
    palmares() {
      if (!this.gala) {
        const palmares = palmaresOf(this.model);
        const show = galaOrdered(this.model, palmares);
        this.gala = { palmares, show: new Show(this.root, show.model, show.palmares) };
      }
      return this.gala;
    }
    render() {
      const s = this.state;
      const ev = s.event;
      this.barEl.innerHTML = bar(this.model, s.person === null && ev === null && !s.palmares ? s.sel : "");
      const favs = favsOf(this.win);
      this.toolsEl.innerHTML = headerTools(this.model, s.dq, favs, s.favOpen);
      this.cells = [];
      if (s.person !== null && s.kit && this.kit && !this.kitOpen.has(s.person)) this.kitGate(s.person, false);
      else if (s.person !== null && s.kit && this.kit) {
        const ctx = kitContext(
          this.model,
          s.person,
          this.viewEl,
          this.kit.assets,
          this.root.lang,
          this.kit.payload,
          (h) => {
            this.kitBack = h;
          }
        );
        const person = s.person;
        void this.kit.loader.open(ctx, () => this.state.kit && this.state.person === person);
      } else if (s.person !== null) {
        const page = riderPage(this.model, s.person, favs.includes(s.person));
        this.viewEl.innerHTML = page.html;
        this.cells = page.cells;
      } else if (ev !== null) {
        const e = this.model.calendar.find((x) => x.event === ev.name && x.start === ev.start);
        this.viewEl.innerHTML = eventPage(this.model, e, ev.cls);
      } else if (s.palmares && !this.galaOpen)
        this.viewEl.innerHTML = palmaresGate(
          this.model.season,
          galaUrl(this.win.location.href, this.model.season, true),
          this.galaWrong
        );
      else if (s.palmares) this.viewEl.innerHTML = palmaresPage(this.model, this.palmares().palmares, this.quit);
      else if (s.sel === "overview")
        this.viewEl.innerHTML = dashboard(this.model, this.galaOpen ? palmaresButton(this.model.season) : "", this.quit);
      else this.viewEl.innerHTML = championship(this.model, this.model.byCode.get(s.sel), s);
      this.$(".tabs [aria-selected=true]")?.scrollIntoView({ block: "nearest", inline: "center" });
      scrollCalendars(this.root);
      for (const row2 of this.root.querySelectorAll(".tabs-wrap .tabs, .fwrap .fscroll")) {
        this.arrows(row2);
        row2.addEventListener("scroll", () => {
          this.arrows(row2);
        });
      }
      if (s.slide >= 0) {
        const { show } = this.palmares();
        s.slide = show.clamp(s.slide);
        show.draw(s.slide, false);
      } else this.gala?.show.hide();
      updatesOf(this.win.document)?.hold(s.slide >= 0);
    }
    /**
     * The media kit's code screen for `person`: the code typed goes through
     * the PHP's digest (kitCodeDigest) and opens the form when it is the
     * rider's; else the screen again, « Code incorrect. ». A code that opened
     * it stays good while the page is open.
     */
    kitGate(person, wrong) {
      this.viewEl.innerHTML = kitGate(this.model.season, wrong);
      const form = this.viewEl.querySelector("form");
      const input = form.querySelector("input");
      if (wrong) input.focus();
      form.addEventListener("submit", (ev) => {
        ev.preventDefault();
        void kitCodeDigest(input.value, this.model.season).then((digest) => {
          if (!this.state.kit || this.state.person !== person) return;
          if (digest !== this.model.mediaKit.get(person)) {
            this.kitGate(person, true);
            return;
          }
          this.kitOpen.add(person);
          this.render();
        });
      });
    }
    /** The arrows of a row that scrolls (the class tabs, the class chips) show only on a side it can scroll to. */
    arrows(row2) {
      const wrap = row2.parentElement;
      wrap.classList.toggle("can-l", row2.scrollLeft > 2);
      wrap.classList.toggle("can-r", row2.scrollLeft + row2.clientWidth < row2.scrollWidth - 2);
    }
    // ---- history ----
    place() {
      const s = this.state;
      return {
        sel: s.sel,
        cls: s.cls[s.sel] || null,
        person: s.person,
        kit: s.kit,
        event: s.event,
        palmares: s.palmares,
        slide: s.slide,
        y: this.win.scrollY,
        depth: s.depth
      };
    }
    apply(place2) {
      const s = this.state;
      s.sel = place2.sel;
      s.person = place2.person;
      s.kit = place2.kit === true;
      s.event = place2.event;
      s.palmares = place2.palmares;
      s.slide = place2.slide;
      s.depth = place2.depth;
      s.sort = null;
      s.cls[place2.sel] = place2.cls ?? "";
    }
    writeHistory(push) {
      const place2 = this.place();
      const url2 = urlOf(this.win.location.href, this.model, place2);
      if (push) this.win.history.pushState(place2, "", url2);
      else this.win.history.replaceState(place2, "", url2);
    }
    /** One navigation: remember where we were (scroll included), change, draw, add an entry. */
    navigate(change2) {
      this.writeHistory(false);
      this.state.palmares = false;
      this.state.slide = -1;
      this.state.favOpen = false;
      this.state.kit = false;
      change2();
      this.render();
      (this.$(".rcard.hit") ?? this.barEl).scrollIntoView({ block: "start" });
      this.state.depth++;
      this.writeHistory(true);
    }
    go(code, cls) {
      this.navigate(() => {
        const s = this.state;
        s.person = null;
        s.event = null;
        s.sel = code;
        s.sort = null;
        s.cls[code] = cls;
      });
    }
    openClass(cls) {
      this.navigate(() => {
        const s = this.state;
        s.cls[s.sel] = cls;
        s.sort = null;
      });
    }
    openPerson(name2) {
      this.navigate(() => {
        this.state.person = name2;
        this.state.event = null;
        this.search.picked();
      });
    }
    /** `cls`: open it on that class's final (a disc of the Palmarès). */
    openEvent(name2, start, cls) {
      this.navigate(() => {
        this.state.person = null;
        this.state.event = cls === void 0 ? { name: name2, start } : { name: name2, start, cls };
      });
    }
    /** Opens or closes the header's favourites panel, without drawing the page again. */
    favPanel(open) {
      this.state.favOpen = open;
      this.$(".favpanel").hidden = !open;
      this.$(".favbtn").setAttribute("aria-expanded", String(open));
    }
    // ---- the show ----
    /** « ▶ Lancer la présentation »: the show from its first slide, full screen. */
    openShow() {
      const { show } = this.palmares();
      show.toc = false;
      this.toSlide(0, true);
      show.fullscreen();
    }
    /** Goes to a slide of the show (kept in the address, without a new history entry); `entering`: it plays its entrance. */
    toSlide(n, entering) {
      const { show } = this.palmares();
      show.toc = false;
      const next = show.clamp(n);
      this.state.slide = next;
      this.writeHistory(false);
      show.draw(next, entering);
    }
    /** Next: one more place of a podium (« Auto »: a podium still coming in, whole at once), else the next slide comes in. */
    next() {
      const { show } = this.palmares();
      const s = this.state.slide;
      if (!show.step() && s < show.slides.length - 1) this.toSlide(s + 1, true);
    }
    /** Back: the slide before, shown whole. */
    back() {
      this.toSlide(this.state.slide - 1, false);
    }
    quitShow() {
      this.state.slide = -1;
      this.writeHistory(false);
      this.palmares().show.hide();
      updatesOf(this.win.document)?.hold(false);
    }
    toggleToc(on) {
      const { show } = this.palmares();
      show.toc = on;
      if (on) show.draw(this.state.slide, false);
      else show.redraw();
    }
    /** The first slide of the `n`th championship (1 = the first), of the highlights (0), or -1. */
    chapter(n) {
      const slides = this.palmares().show.slides;
      if (n === 0) {
        const fact = slides.findIndex((x) => x.sub === T.gala.factsTitle);
        return fact >= 0 ? fact : slides.findIndex((x) => x.part === "end");
      }
      const firsts = slides.flatMap(
        (x, j) => x.part !== "start" && x.part !== "end" && slides[j - 1].part !== x.part ? [j] : []
      );
      return n <= firsts.length ? firsts[n - 1] : -1;
    }
    /** A key while the show is open; false when the show doesn't use it. */
    showKey(k) {
      const { show } = this.palmares();
      if ((k === "i" || k === "I") && !show.toc) {
        const info = this.$(".gl-tools [data-help]");
        if (!info) return false;
        this.openHelp(info.dataset.help, info);
        return true;
      }
      const chapter = /^[1-9]$/.test(k) ? this.chapter(Number(k)) : k === "s" || k === "S" ? this.chapter(0) : -1;
      if (chapter >= 0) this.toSlide(chapter, true);
      else if (k === "Home") this.toSlide(0, true);
      else if (k === "End") this.toSlide(show.slides.length - 1, true);
      else if (k === "m" || k === "M" || k === "g" || k === "G") this.toggleToc(!show.toc);
      else if (k === "Escape") {
        if (show.toc) this.toggleToc(false);
        else this.quitShow();
      } else if (show.toc) return false;
      else if (NEXT_KEYS.includes(k)) this.next();
      else if (PREV_KEYS.includes(k)) this.back();
      else if (k === "f" || k === "F") show.fullscreen();
      else if (k === "a" || k === "A") show.setAuto(!show.auto);
      else return false;
      return true;
    }
    /** A click in the show: its tools, the table of contents, ‹ ›, or anywhere else (next). */
    showClick(target) {
      const { show } = this.palmares();
      if (this.win.performance.now() - this.swipedAt < SWIPE_CLICK_MS) return;
      const b = target.closest("button");
      if (b) {
        const d = b.dataset;
        if (d.go !== void 0) this.toSlide(Number(d.go), true);
        else if (d.toc) this.toggleToc(d.toc === "1");
        else if (d.step) {
          if (d.step === "1") this.next();
          else this.back();
        } else if (d.fs) show.fullscreen();
        else if (d.mode) show.setAuto(!show.auto);
        else this.quitShow();
      } else if (!target.closest(".gl-toc")) this.next();
    }
    /**
     * A swipe on the show (a phone, a tablet): to the left next, to the right back. Never with a second
     * finger (a pinch zooms), nor while the show is zoomed (one finger moves the view).
     */
    listenSwipe() {
      let from = null;
      this.root.addEventListener("pointerdown", (ev) => {
        from = this.state.slide >= 0 && ev.isPrimary && ev.target.closest(".gl-show") && !ev.target.closest(".gl-toc, dialog") && !this.palmares().show.zoomed() ? { x: ev.clientX, y: ev.clientY } : null;
      });
      this.root.addEventListener("pointercancel", () => {
        from = null;
      });
      this.root.addEventListener("pointerup", (ev) => {
        if (!from) return;
        const dx = ev.clientX - from.x;
        const dy = ev.clientY - from.y;
        from = null;
        if (Math.abs(dx) < SWIPE_PX || Math.abs(dx) < 1.5 * Math.abs(dy)) return;
        this.swipedAt = this.win.performance.now();
        if (dx < 0) this.next();
        else this.back();
      });
    }
    // ---- events ----
    listen() {
      const root = this.root;
      listenSeason(root);
      this.listenSwipe();
      this.win.addEventListener("popstate", (ev) => {
        if (this.kitBack?.()) return;
        const place2 = ev.state ?? placeOf(this.win.location.href, this.model);
        this.apply(place2);
        this.render();
        this.win.scrollTo(0, place2.y);
      });
      root.addEventListener("click", (ev) => {
        this.onClick(ev.target);
      });
      root.addEventListener("keydown", (ev) => {
        const row2 = ev.target.closest(".kcard[data-open], [role=link][data-start]");
        if (row2 && ev.target === row2 && (ev.key === "Enter" || ev.key === " ")) {
          ev.preventDefault();
          const d = row2.dataset;
          if (d.start) this.openEvent(d.event, d.start);
          else this.openClass(d.open);
        }
      });
      const doc = root.ownerDocument;
      doc.addEventListener("keydown", (ev) => {
        const help = this.$("dialog.help[open]");
        if (help) {
          if (ev.key === "Escape" && help.classList.contains("fallback")) this.closeHelp(help);
        } else if (this.state.slide >= 0) {
          if (this.showKey(ev.key)) ev.preventDefault();
        } else if (ev.key === "Escape") {
          if (this.state.favOpen) {
            this.favPanel(false);
            this.$(".favbtn").focus();
          }
        }
      });
    }
    onClick(target) {
      if (!target.isConnected) return;
      if (this.state.favOpen && !target.closest(".favwrap")) this.favPanel(false);
      if (target.closest(".ypick")) return;
      if (target.closest(".mk-view") && !target.closest("[data-back]")) return;
      const help = target.closest("dialog.help");
      if (help && (target === help || target.closest("[data-help-close]"))) {
        this.closeHelp(help);
        return;
      }
      if (help) return;
      const mark = target.closest("[data-help]");
      if (mark) {
        this.openHelp(mark.dataset.help, mark);
        return;
      }
      const s = this.state;
      if (target.closest(".gl-show")) {
        this.showClick(target);
        return;
      }
      const th = target.closest("th[data-sort]");
      if (th) {
        const key = th.dataset.sort;
        if (key === "RANKING") s.sort = null;
        else if (s.sort?.key === key) s.sort = { key, dir: s.sort.dir === "descending" ? "ascending" : "descending" };
        else s.sort = { key, dir: key === "NOM" ? "ascending" : "descending" };
        this.render();
        return;
      }
      if (target.closest("a")) return;
      if (target.closest("form")) return;
      const person = target.closest("[data-person]");
      if (person) {
        this.openPerson(person.dataset.person);
        return;
      }
      const race = target.closest("[data-start]");
      if (race) {
        this.openEvent(race.dataset.event, race.dataset.start, race.dataset.final);
        return;
      }
      const open = target.closest("[data-open]");
      if (open) {
        this.openClass(open.dataset.open);
        return;
      }
      const b = target.closest("button");
      if (!b) return;
      const d = b.dataset;
      if (d.fav) {
        toggleFav(this.win, d.fav);
        this.render();
      } else if (d.favs) this.favPanel(!s.favOpen);
      else if (d.kit)
        this.navigate(() => {
          s.kit = true;
        });
      else if (d.back) {
        if (s.depth > 0) this.win.history.back();
        else if (s.kit) {
          s.kit = false;
          this.render();
          this.barEl.scrollIntoView({ block: "start" });
          this.writeHistory(false);
        } else
          this.navigate(() => {
            s.person = null;
            s.event = null;
          });
      } else if (d.sel) {
        const sel = d.sel;
        this.navigate(() => {
          s.person = null;
          s.event = null;
          s.sel = sel;
          s.sort = null;
          s.cls[sel] = "";
        });
      } else if (d.palmares)
        this.navigate(() => {
          s.palmares = true;
        });
      else if (d.play) this.openShow();
      else if (d.jump) {
        const code = d.jump;
        const section = [...this.root.querySelectorAll(".gl-champ")].find((x) => x.id === "gl-" + code);
        section.scrollIntoView({ behavior: "smooth", block: "start" });
      } else if (d.go) this.go(d.go, d.cls);
      else if (d.cell) this.showCell(d.cell);
      else if (d.copy) this.copyLink(b.querySelector("span"));
      else if (d.allcls) {
        this.navigate(() => {
          s.cls[s.sel] = "";
        });
      } else if (d.scroll) {
        const row2 = b.parentElement.querySelector(".tabs, .fscroll");
        row2.scrollLeft += Number(d.scroll) * row2.clientWidth * 0.7;
        this.arrows(row2);
      } else if (d.cls !== void 0) {
        const cls = d.cls;
        this.navigate(() => {
          s.cls[s.sel] = cls;
          s.sort = null;
        });
      } else if (d.filter !== void 0) {
        const f = d.filter;
        const cur = s.filter[s.sel] ?? [];
        s.filter[s.sel] = f === "" ? [] : cur.includes(f) ? cur.filter((x) => x !== f) : [...cur, f];
        this.render();
      } else {
        this.search.click(b);
      }
    }
    /** Selects a cell of the season grid and fills the detail line. */
    showCell(id) {
      const [i, j] = id.split("-").map(Number);
      const out = this.$(".gdetail");
      for (const b of this.root.querySelectorAll(".gc.sel")) b.classList.remove("sel");
      this.$('[data-cell="' + id + '"]').classList.add("sel");
      out.innerHTML = gridDetail(this.model, this.cells[i][j]);
    }
    /**
     * Opens a help (the season grid's legend) in the browser's top layer: never cut by a short table or a
     * scrolling box. Without showModal (an old browser), the same box fixed over the page.
     */
    openHelp(key, button) {
      const dialog = this.$('dialog[data-help-for="' + key + '"]');
      const back = () => {
        button.focus();
      };
      if (typeof dialog.showModal === "function") {
        dialog.addEventListener("close", back, { once: true });
        dialog.showModal();
      } else {
        dialog.classList.add("fallback");
        dialog.setAttribute("open", "");
        this.helpBack = back;
      }
      dialog.querySelector("[data-help-close]").focus();
    }
    /** Closes a help: the browser's way (its close event gives the focus back), or the fallback's. */
    closeHelp(dialog) {
      if (!dialog.classList.contains("fallback")) {
        dialog.close();
        return;
      }
      dialog.removeAttribute("open");
      dialog.classList.remove("fallback");
      this.helpBack();
    }
    /**
     * Copies the page's address and says so in the label for 2 s. When the
     * clipboard refuses, the label shows the address itself.
     */
    copyLink(label2) {
      const href = this.win.location.href;
      const failed = () => {
        label2.textContent = href;
      };
      try {
        this.win.navigator.clipboard.writeText(href).then(() => {
          label2.textContent = T.linkCopied;
          this.win.setTimeout(() => {
            label2.textContent = T.copyLink;
          }, 2e3);
        }, failed);
      } catch {
        failed();
      }
    }
  };

  // plugins/csm-standings/src/site/main.ts
  window.csmClassements = (host, payload) => {
    mountClassements(rootIn(host), payload);
  };
  window.csmVerification = (host, payload) => {
    mountVerification(rootIn(host), payload);
  };
  window.csmNotice = (host) => {
    rootIn(host);
  };
})();
	</script>
	{"id":23883,"date":"2026-09-26T16:55:38","date_gmt":"2026-09-26T20:55:38","guid":{"rendered":"https:\/\/canadiansupermoto.com\/?page_id=23883"},"modified":"2026-09-28T20:20:46","modified_gmt":"2026-09-29T00:20:46","slug":"standings","status":"publish","type":"page","link":"https:\/\/canadiansupermoto.com\/en\/standings\/","title":{"rendered":"Standings"},"content":{"rendered":"\t<csm-classements id=\"csm-classements-1-f2690a\" lang=\"en\"><div class=\"csm-widget csm-classements\"><\/div><\/csm-classements>\n\t<script>\n\t(function(){\n\t\tvar host = document.getElementById(\"csm-classements-1-f2690a\");\n\t\tif (host) csmClassements(host, {\"today\":\"2026-10-25\",\"tabNames\":[\"SEASONS\",\"MEDIA KIT\",\"SPONSORS\",\"PROMOTIONS\",\"DISTANCES\",\"VENUES\",\"CONFIG\",\"CALENDAR\",\"GALA\",\"HIGHLIGHTS\",\"CP - 2026\",\"CSM - 2026\",\"SBQ - 2026\",\"SMO - 2026\",\"SMQ - 2026\"],\"seasons\":[[\"SEASON\",\"RULEBOOK\",\"RULEBOOK URL\",\"RULEBOOK URL EN\"],[\"2026\",\"2026\",\"https:\\\/\\\/canadiansupermoto.com\\\/reglements\\\/\",\"https:\\\/\\\/canadiansupermoto.com\\\/en\\\/rules\\\/\"]],\"venues\":[[\"CODE\",\"NAME\",\"ADDRESS\",\"COORDINATES\"],[\"KTR\",\"Karting Trois-Rivi\\u00e8res\",\"4150 Rang Saint-Charles, Trois-Rivi\\u00e8res, QC G9B 7W9\",\"46.30579907818198, -72.6905438\"],[\"SHANNONVILLE\",\"Shannonville Motorsport Park\",\"7047 Old Highway 2, Tyendinaga, ON K0K 3A0\",\"44.224923220973814, -77.1591345\"],[\"MECAGLISSE\",\"Circuit M\\u00e9caglisse\",\"1253 Chem. Dufresne, Notre-Dame-de-la-Merci, QC J0T 2A0\",\"46.21861015578252, -74.10801541601303\"],[\"RDL\",\"Rivi\\u00e8re-du-Loup\",\"320 Rue Saint-Pierre, Rivi\\u00e8re-du-Loup, QC G5R 3V3\",\"47.825519874764915, -69.54155227116394\"],[\"MONTMAGNY\",\"Autodrome Montmagny\",\"455 Chemin St L\\u00e9on, Montmagny, QC G5V 3S9\",\"46.93829935776403, -70.55348868714374\"],[\"SHAWINIGAN\",\"Shawinigan\",\"1 Rue Jacques-Plante, Shawinigan, QC G9N 0B7\",\"46.558720786614664, -72.74311380511871\"],[\"GP3R\",\"Trois-Rivi\\u00e8res\",\"1450 Rue de l'Hippodrome, Trois-Rivi\\u00e8res, QC G9A 5L9\",\"46.345324499739895, -72.55590722883606\"],[\"LOMBARDY\",\"Lombardy\",\"6 Kellys Rd, Lombardy, ON K0G 1L0\",\"44.834189738380125, -76.07022880258425\"]],\"config\":[[\"ORDER\",\"SITE ORDER\",\"GALA ORDER\",\"SEASON\",\"CODE\",\"TAB\",\"NAME\",\"NAME EN\",\"LOGO\",\"RULEBOOK\",\"RULEBOOK URL\",\"RULEBOOK URL EN\",\"COLOR\",\"CLASSES\",\"SITE CLASS ORDER\"],[\"1\",\"1\",\"5\",\"2026\",\"CSM\",\"CSM - 2026\",\"Championnat Canadien de Supermoto\",\"Canadian Supermoto Championship\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/logos2-CSM-1024x534.png\",\"2026\",\"https:\\\/\\\/canadiansupermoto.com\\\/reglements\\\/\",\"https:\\\/\\\/canadiansupermoto.com\\\/en\\\/rules\\\/\",\"#D9302A\",\"SM 1, SM 2, SM 3, SM 4, SM 5, SM 6, SM 7, SM 8, SM 9, SM 10, SM 11, SM 12, MINI MOTO 1, MINI MOTO 2, MINI MOTO 3, MINI MOTO 4, SS 1, SBK 1, SS 2, SBK 2, SBK 3, SBK 4, LW\",\"SM 1, SM 2, SM 3, SM 4, SM 5, SM 6, SM 7, SM 8, SM 9, SM 10, SM 11, SM 12, MINI MOTO 1, MINI MOTO 2, MINI MOTO 3, MINI MOTO 4, SS 1, SBK 1, SS 2, SBK 2, SBK 3, SBK 4, LW\"],[\"2\",\"3\",\"3\",\"2026\",\"SMQ\",\"SMQ - 2026\",\"Championnat Supermoto Qu\\u00e9bec\",\"Quebec Supermoto Championship\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/logos-SMQ-1024x558.png\",\"2026\",\"https:\\\/\\\/canadiansupermoto.com\\\/reglements\\\/\",\"https:\\\/\\\/canadiansupermoto.com\\\/en\\\/rules\\\/\",\"#2E3790\",\"SM 1, SM 2, SM 3, SM 4, SM 5, SM 6, SM 7, SM 8, SM 9, SM 10, SM 11, SM 12, MINI MOTO 1, MINI MOTO 2, MINI MOTO 3, MINI MOTO 4, SS 1, SBK 1, SS 2, SBK 2, SBK 3, SBK 4, LW\",\"SM 1, SM 2, SM 3, SM 4, SM 5, SM 6, SM 7, SM 8, SM 9, SM 10, SM 11, SM 12, MINI MOTO 1, MINI MOTO 2, MINI MOTO 3, MINI MOTO 4, SS 1, SBK 1, SS 2, SBK 2, SBK 3, SBK 4, LW\"],[\"3\",\"4\",\"2\",\"2026\",\"SMO\",\"SMO - 2026\",\"Championnat Supermoto Ontario\",\"Ontario Supermoto Championship\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/logos-SMO-1024x529.png\",\"2026\",\"https:\\\/\\\/canadiansupermoto.com\\\/reglements\\\/\",\"https:\\\/\\\/canadiansupermoto.com\\\/en\\\/rules\\\/\",\"#B8283A\",\"SM 1, SM 2, SM 3, SM 4, SM 5, SM 6, SM 7, SM 8, SM 9, SM 10, SM 11, SM 12, MINI MOTO 1, MINI MOTO 2, MINI MOTO 3, MINI MOTO 4, SS 1, SBK 1, SS 2, SBK 2, SBK 3, SBK 4, LW\",\"SM 1, SM 2, SM 3, SM 4, SM 5, SM 6, SM 7, SM 8, SM 9, SM 10, SM 11, SM 12, MINI MOTO 1, MINI MOTO 2, MINI MOTO 3, MINI MOTO 4, SS 1, SBK 1, SS 2, SBK 2, SBK 3, SBK 4, LW\"],[\"4\",\"2\",\"4\",\"2026\",\"CP\",\"CP - 2026\",\"Coupe Performance Qu\\u00e9bec\",\"Quebec Performance Cup\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/logos-CP-1024x551.png\",\"2026\",\"https:\\\/\\\/canadiansupermoto.com\\\/reglements\\\/\",\"https:\\\/\\\/canadiansupermoto.com\\\/en\\\/rules\\\/\",\"#E0141B\",\"SM 1, SM 2, SM 3, SM 4, SM 5, SM 6, SM 7, SM 8, SM 9, SM 10, SM 11, SM 12, MINI MOTO 1, MINI MOTO 2, MINI MOTO 3, MINI MOTO 4, SS 1, SBK 1, SS 2, SBK 2, SBK 3, SBK 4, LW\",\"SM 1, SM 2, SM 3, SM 4, SM 5, SM 6, SM 7, SM 8, SM 9, SM 10, SM 11, SM 12, MINI MOTO 1, MINI MOTO 2, MINI MOTO 3, MINI MOTO 4, SS 1, SBK 1, SS 2, SBK 2, SBK 3, SBK 4, LW\"],[\"5\",\"5\",\"1\",\"2026\",\"SBQ\",\"SBQ - 2026\",\"Championnat Superbiker Moto MPH\",\"Superbiker Moto MPH Championship\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/logos-SBQ-1024x551.png\",\"2026\",\"https:\\\/\\\/canadiansupermoto.com\\\/reglements\\\/\",\"https:\\\/\\\/canadiansupermoto.com\\\/en\\\/rules\\\/\",\"#27307F\",\"SM 1, SM 2, SM 3, SM 4, SM 5, SM 6, SM 7, SM 8, SM 9, SM 10, SM 11, SM 12, MINI MOTO 1, MINI MOTO 2, MINI MOTO 3, MINI MOTO 4, SS 1, SBK 1, SS 2, SBK 2, SBK 3, SBK 4, LW\",\"SM 1, SM 2, SM 3, SM 4, SM 5, SM 6, SM 7, SM 8, SM 9, SM 10, SM 11, SM 12, MINI MOTO 1, MINI MOTO 2, MINI MOTO 3, MINI MOTO 4, SS 1, SBK 1, SS 2, SBK 2, SBK 3, SBK 4, LW\"]],\"calendar\":[[\"SEASON\",\"EVENT\",\"START\",\"END\",\"RACE DAYS\",\"VENUE\",\"VENUE CODE\",\"CHAMPIONSHIPS\",\"SCHEDULE\",\"SCHEDULE EN\"],[\"2026\",\"KTR\",\"2026-05-01\",\"2026-05-03\",\"2026-05-03\",\"Karting Trois-Rivi\\u00e8res\",\"KTR\",\"CP\"],[\"2026\",\"SHANNONVILLE\",\"2026-05-15\",\"2026-05-17\",\"2026-05-16,2026-05-17\",\"Shannonville (Ont.)\",\"SHANNONVILLE\",\"CSM, SMQ, SMO\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/Shannonville.pdf\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/Shannonville.pdf\"],[\"2026\",\"MECAGLISSE\",\"2026-06-12\",\"2026-06-14\",\"2026-06-14\",\"M\\u00e9caglisse X-Fight\",\"MECAGLISSE\",\"CSM, SMQ, SBQ\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/horaire-mecaglisse_v2.pdf\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/horaire-mecaglisse_v2.pdf\"],[\"2026\",\"SKRDL\",\"2026-07-17\",\"2026-07-19\",\"2026-07-18,2026-07-19\",\"Centre-ville de Rivi\\u00e8re-du-Loup\",\"RDL\",\"CSM, SMQ, CP\"],[\"2026\",\"MONTMAGNY 1\",\"2026-07-25\",\"2026-07-26\",\"2026-07-26\",\"Pete Moto Cup 1 (Montmagny)\",\"MONTMAGNY\",\"SBQ\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/horraire-montmagny.jpeg\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/horraire-montmagny.jpeg\"],[\"2026\",\"SHAWINIGAN\",\"2026-07-31\",\"2026-08-02\",\"2026-08-01,2026-08-02\",\"Grand Prix de Shawinigan\",\"SHAWINIGAN\",\"SMQ, CP\"],[\"2026\",\"GP3R-S\",\"2026-08-22\",\"2026-08-22\",\"2026-08-22\",\"GP3R Trois-Rivi\\u00e8res (samedi)\",\"GP3R\",\"CSM, SMQ\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/Horaire_RallyX_2026_v2_precis_debut_fin.pdf\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/Horaire_RallyX_2026_v2_precis_debut_fin.pdf\"],[\"2026\",\"GP3R-D\",\"2026-08-23\",\"2026-08-23\",\"2026-08-23\",\"GP3R Trois-Rivi\\u00e8res (dimanche)\",\"GP3R\",\"CSM, SMQ\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/Horaire_RallyX_2026_v2_precis_debut_fin.pdf\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/Horaire_RallyX_2026_v2_precis_debut_fin.pdf\"],[\"2026\",\"MONTMAGNY 2\",\"2026-08-29\",\"2026-08-30\",\"2026-08-30\",\"Coupe Pete Moto (Montmagny)\",\"MONTMAGNY\",\"SMQ, SBQ\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/Montmagny-29-30-aout-2026.pdf\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/Montmagny-29-30-aout-2026.pdf\"],[\"2026\",\"LOMBARDY\",\"2026-09-18\",\"2026-09-20\",\"2026-09-19,2026-09-20\",\"Karting Lombardy (Ont.)\",\"LOMBARDY\",\"CSM, SMO\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/Horaire_Lombardy_18-20_septembre_2026.pdf\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/Horaire_Lombardy_18-20_septembre_2026.pdf\"],[\"2026\",\"KTR\",\"2026-10-09\",\"2026-10-11\",\"2026-10-10\",\"Karting Trois-Rivi\\u00e8res\",\"KTR\",\"CSM, SMQ\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/10\\\/KTR_9-11_octobre_2026_FR.pdf\",\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/uploads\\\/2026\\\/10\\\/KTR_9-11_octobre_2026_EN.pdf\"]],\"tabs\":{\"CP - 2026\":[[\"CATEGORY\",\"#\",\"NAME\",\"KTR\",\"SKRDL\",\"SHAWINIGAN\"],[\"LW\",\"22\",\"ZAIM LAFLAMME\",\"20\",\"25\",\"25\"],[\"LW\",\"222\",\"CHELSEA PARENT-BOURSIER\",\"25\",\"22\",\"22\"],[\"LW\",\"145\",\"GABRIEL ST-PIERRE\",\"22\",\"16\",\"20\"],[\"LW\",\"922\",\"JEAN-PHILIPPE AUDET HALL\",\"18\",\"18\",\"DNS\"],[\"LW\",\"24\",\"YANNICK LAFLAMME\",\"DNS\",\"15\",\"18\"],[\"LW\",\"439\",\"JULIE SIMARD\",\"16\",\"14\",\"DNS\"],[\"LW\",\"29\",\"AUDREY FRAMAND\",\"DNS\",\"13\",\"16\"],[\"LW\",\"717\",\"TOMMY PAQUETTE\",\"X\",\"20\",\"X\"],[\"LW\",\"9\",\"DIMITRI GODARD\",\"X\",\"DNS\",\"DNS\"],[\"LW\",\"34\",\"DAVE AUGER\",\"X\",\"DNS\",\"X\"],[\"LW\",\"292\",\"FR\\u00c9D\\u00c9RIC GAUTHIER DOYLE\",\"DNS\",\"X\",\"X\"],[\"MINI MOTO 1\",\"213\",\"JONATHAN MASSIGNANI\",\"22\",\"25\",\"22\"],[\"MINI MOTO 1\",\"51\",\"MARTIN PARENT\",\"25\",\"20\",\"20\"],[\"MINI MOTO 1\",\"16\",\"SYLVAIN FOLLENFANT\",\"DNS\",\"22\",\"25\"],[\"MINI MOTO 1\",\"68\",\"JAGER STOCKILL\",\"X\",\"DNF\",\"X\"],[\"MINI MOTO 2\",\"222\",\"CHELSEA PARENT-BOURSIER\",\"25\",\"25\",\"25\"],[\"MINI MOTO 2\",\"22\",\"JULIE LALONDE\",\"22\",\"20\",\"22\"],[\"MINI MOTO 2\",\"21\",\"MAXIME BELLEROPHON\",\"18\",\"22\",\"X\"],[\"MINI MOTO 2\",\"313\",\"SARAH MICHELLE COTTON\",\"20\",\"18\",\"DNS\"],[\"MINI MOTO 3\",\"686\",\"FRED MONTMINY SAMSON\",\"22\",\"22\",\"25\"],[\"MINI MOTO 3\",\"59\",\"CLAUDE DENONCOURT\",\"15\",\"25\",\"22\"],[\"MINI MOTO 3\",\"66\",\"RYAN MORIN\",\"20\",\"14\",\"16\"],[\"MINI MOTO 3\",\"1\",\"OCTAVIA PINEL BOURSIER\",\"14\",\"15\",\"15\"],[\"MINI MOTO 3\",\"439\",\"JULIE SIMARD\",\"25\",\"16\",\"DNS\"],[\"MINI MOTO 3\",\"42\",\"JAYSON MONGEON\",\"X\",\"18\",\"20\"],[\"MINI MOTO 3\",\"29\",\"AUDREY FRAMAND\",\"18\",\"20\",\"DNS\"],[\"MINI MOTO 3\",\"905\",\"ABBY DUBUC\",\"X\",\"X\",\"18\"],[\"MINI MOTO 3\",\"28\",\"NATHALIE VEZEAU\",\"16\",\"DNS\",\"X\"],[\"MINI MOTO 3\",\"33\",\"MARTINE BUSSI\\u00c8RE\",\"X\",\"DNS\",\"DNS\"],[\"MINI MOTO 3\",\"12\",\"ZAYDAN DOHA\",\"DNS\",\"X\",\"X\"],[\"MINI MOTO 4\",\"66\",\"RYAN MORIN\",\"22\",\"20\",\"20\"],[\"MINI MOTO 4\",\"1\",\"OCTAVIA PINEL BOURSIER\",\"20\",\"18\",\"18\"],[\"MINI MOTO 4\",\"21\",\"MAXIME BELLEROPHON\",\"25\",\"25\",\"X\"],[\"MINI MOTO 4\",\"42\",\"JAYSON MONGEON\",\"X\",\"22\",\"25\"],[\"MINI MOTO 4\",\"905\",\"ABBY DUBUC\",\"DNF\",\"X\",\"22\"],[\"MINI MOTO 4\",\"5\",\"EMY JADE VALL\\u00c9E\",\"X\",\"X\",\"16\"],[\"MINI MOTO 4\",\"5\",\"JAYDEN VALL\\u00c9E\",\"DNS\",\"X\",\"X\"],[\"SM 1\",\"1\",\"NELSON JR MOREAU\",\"25\",\"12\",\"25\"],[\"SM 1\",\"93\",\"ALEX DESHAIES\",\"15\",\"22\",\"15\"],[\"SM 1\",\"21\",\"XAVIER BELLEROPHON\",\"22\",\"25\",\"DNS\"],[\"SM 1\",\"654\",\"JACOB BILODEAU\",\"X\",\"20\",\"22\"],[\"SM 1\",\"341\",\"DAVID BERTRAND\",\"X\",\"X\",\"DNS\"],[\"SM 1\",\"22\",\"ZAIM LAFLAMME\",\"20\",\"16\",\"DNF\"],[\"SM 1\",\"104\",\"MAVRICK CYR\",\"X\",\"14\",\"20\"],[\"SM 1\",\"44\",\"GABRIEL VALL\\u00c9E\",\"16\",\"X\",\"16\"],[\"SM 1\",\"183\",\"DAVID POTHIER\",\"13\",\"X\",\"18\"],[\"SM 1\",\"94\",\"LUDOVICK S\\u00c9VIGNY\",\"X\",\"15\",\"13\"],[\"SM 1\",\"87\",\"DAVID GUERTIN\",\"X\",\"18\",\"DNS\"],[\"SM 1\",\"198\",\"MARTIN CARPENTIER\",\"18\",\"X\",\"DNS\"],[\"SM 1\",\"24\",\"S\\u00c9BASTIEN TREMBLAY\",\"X\",\"DNS\",\"14\"],[\"SM 1\",\"9\",\"DIMITRI GODARD\",\"14\",\"DNS\",\"DNS\"],[\"SM 1\",\"79\",\"MACKENZIE WEIL\",\"X\",\"13\",\"X\"],[\"SM 1\",\"67\",\"JIMMY MORIN\",\"12\",\"11\",\"DNS\"],[\"SM 1\",\"34\",\"DAVE AUGER\",\"X\",\"DNS\",\"X\"],[\"SM 10\",\"281\",\"VALERIAN RENAUD\",\"15\",\"14\",\"25\"],[\"SM 10\",\"1\",\"CHELSEA PARENT-BOURSIER\",\"22\",\"11\",\"20\"],[\"SM 10\",\"145\",\"GABRIEL ST-PIERRE\",\"18\",\"22\",\"9\"],[\"SM 10\",\"51\",\"MARTIN PARENT\",\"20\",\"12\",\"12\"],[\"SM 10\",\"29\",\"MARIO DUMAS\",\"13\",\"16\",\"11\"],[\"SM 10\",\"10\",\"MARC DUMONT\",\"DNS\",\"25\",\"16\"],[\"SM 10\",\"97\",\"DYLAN SCOTT PAQUETTE\",\"14\",\"9\",\"14\"],[\"SM 10\",\"946\",\"ESTEBAN CANO\",\"16\",\"0\",\"18\"],[\"SM 10\",\"99\",\"BENOIT DRAPEAU\",\"X\",\"13\",\"13\"],[\"SM 10\",\"16\",\"SYLVAIN FOLLENFANT\",\"25\",\"X\",\"X\"],[\"SM 10\",\"450\",\"ALEXIS MARINEAU\",\"X\",\"20\",\"22\"],[\"SM 10\",\"42\",\"DOMINIC ANDR\\u00c9-MONGEON\",\"X\",\"6\",\"15\"],[\"SM 10\",\"404\",\"SHAWN ALEXIS GRONDIN GAUTHIER\",\"DNF\",\"10\",\"10\"],[\"SM 10\",\"11\",\"TOJO\",\"DNF\",\"18\",\"X\"],[\"SM 10\",\"482\",\"KEWINE SEDRICK\",\"X\",\"15\",\"X\"],[\"SM 10\",\"313\",\"SARAH MICHELLE COTTON\",\"12\",\"3\",\"DNS\"],[\"SM 10\",\"17\",\"SALOMON H\\u00c9RARD\",\"X\",\"7\",\"8\"],[\"SM 10\",\"393\",\"RAPHA\\u00cbL LEMIRE\",\"X\",\"8\",\"DNS\"],[\"SM 10\",\"35\",\"CL\\u00c9MENT LAROCHE\",\"X\",\"X\",\"7\"],[\"SM 10\",\"922\",\"JEAN-PHILIPPE AUDET HALL\",\"DNS\",\"5\",\"DNS\"],[\"SM 10\",\"33\",\"S\\u00c9BASTIEN LABELLE\",\"DNS\",\"4\",\"DNS\"],[\"SM 10\",\"24\",\"YANNICK LAFLAMME\",\"DNS\",\"2\",\"DNS\"],[\"SM 10\",\"686\",\"FRED MONTMINY SAMSON\",\"DNS\",\"1\",\"DNS\"],[\"SM 10\",\"533\",\"MARTIN VILLENEUVE\",\"DNS\",\"0\",\"DNS\"],[\"SM 10\",\"117\",\"ALEJANDRO RIVIERA\",\"X\",\"X\",\"DNF\"],[\"SM 10\",\"330\",\"ANTOINE GAGNON\",\"DNS\",\"X\",\"DNS\"],[\"SM 10\",\"11\",\"GABRIEL CHARETTE\",\"X\",\"X\",\"DNS\"],[\"SM 10\",\"40\",\"BEN BELLEVILLE\",\"DNS\",\"X\",\"X\"],[\"SM 10\",\"292\",\"FR\\u00c9D\\u00c9RIC GAUTHIER DOYLE\",\"DNS\",\"X\",\"X\"],[\"SM 11\",\"7\",\"THOMAS MANSEAU\",\"25\",\"18\",\"25\"],[\"SM 11\",\"229\",\"ANTHONY GOSSELIN\",\"22\",\"22\",\"16\"],[\"SM 11\",\"213\",\"JONATHAN MASSIGNANI\",\"20\",\"14\",\"14\"],[\"SM 11\",\"27\",\"JEREMIE LAUZI\\u00c8RE\",\"DNF\",\"25\",\"18\"],[\"SM 11\",\"16\",\"SYLVAIN FOLLENFANT\",\"X\",\"20\",\"22\"],[\"SM 11\",\"78\",\"CLAUDE SERGERIE\",\"15\",\"9\",\"13\"],[\"SM 11\",\"158\",\"THOMAS LAROUCHE\",\"DNS\",\"13\",\"20\"],[\"SM 11\",\"14\",\"FRANCOIS DUMONT LAQUERRE\",\"18\",\"X\",\"12\"],[\"SM 11\",\"13\",\"ROMAIN LANGLET\",\"X\",\"15\",\"15\"],[\"SM 11\",\"38\",\"CHRIS BARTCZAK\",\"16\",\"DNS\",\"DNS\"],[\"SM 11\",\"321\",\"DANY MARCHAND\",\"DNS\",\"16\",\"X\"],[\"SM 11\",\"177\",\"XAVIER MALTAIS\",\"X\",\"12\",\"X\"],[\"SM 11\",\"717\",\"TOMMY PAQUETTE\",\"X\",\"11\",\"X\"],[\"SM 11\",\"68\",\"JAGER STOCKILL\",\"X\",\"10\",\"X\"],[\"SM 11\",\"74\",\"CEDRICK BOURGEOIS TURCOTTE\",\"X\",\"DNF\",\"DNF\"],[\"SM 11\",\"75\",\"FRANCK NUNES\",\"DNF\",\"X\",\"DNS\"],[\"SM 12\",\"1\",\"NELSON JR MOREAU\",\"25\",\"25\",\"22\"],[\"SM 12\",\"22\",\"ZAIM LAFLAMME\",\"20\",\"22\",\"15\"],[\"SM 12\",\"24\",\"S\\u00c9BASTIEN TREMBLAY\",\"X\",\"18\",\"25\"],[\"SM 12\",\"93\",\"ALEX DESHAIES\",\"DNS\",\"20\",\"20\"],[\"SM 12\",\"21\",\"XAVIER BELLEROPHON\",\"22\",\"16\",\"X\"],[\"SM 12\",\"654\",\"JACOB BILODEAU\",\"X\",\"13\",\"18\"],[\"SM 12\",\"104\",\"MAVRICK CYR\",\"X\",\"14\",\"14\"],[\"SM 12\",\"405\",\"DORIAN PRIEM\",\"18\",\"X\",\"X\"],[\"SM 12\",\"44\",\"GABRIEL VALL\\u00c9E\",\"DNS\",\"X\",\"16\"],[\"SM 12\",\"198\",\"MARTIN CARPENTIER\",\"16\",\"X\",\"DNS\"],[\"SM 12\",\"9\",\"DIMITRI GODARD\",\"X\",\"15\",\"DNS\"],[\"SM 12\",\"183\",\"DAVID POTHIER\",\"15\",\"X\",\"DNS\"],[\"SM 12\",\"67\",\"JIMMY MORIN\",\"DNS\",\"12\",\"DNS\"],[\"SM 12\",\"94\",\"LUDOVICK S\\u00c9VIGNY\",\"X\",\"11\",\"DNS\"],[\"SM 12\",\"79\",\"MACKENZIE WEIL\",\"X\",\"10\",\"X\"],[\"SM 12\",\"87\",\"DAVID GUERTIN\",\"X\",\"9\",\"DNS\"],[\"SM 12\",\"341\",\"DAVID BERTRAND\",\"X\",\"X\",\"DNS\"],[\"SM 12\",\"34\",\"DAVE AUGER\",\"X\",\"DNS\",\"X\"],[\"SM 12\",\"24\",\"MICHAEL MICHAUD\",\"DNS\",\"X\",\"X\"],[\"SM 2\",\"158\",\"THOMAS LAROUCHE\",\"25\",\"20\",\"25\"],[\"SM 2\",\"321\",\"DANY MARCHAND\",\"13\",\"25\",\"22\"],[\"SM 2\",\"27\",\"JEREMIE LAUZI\\u00c8RE\",\"22\",\"18\",\"18\"],[\"SM 2\",\"7\",\"THOMAS MANSEAU\",\"18\",\"12\",\"20\"],[\"SM 2\",\"229\",\"ANTHONY GOSSELIN\",\"20\",\"13\",\"13\"],[\"SM 2\",\"13\",\"ROMAIN LANGLET\",\"X\",\"22\",\"16\"],[\"SM 2\",\"213\",\"JONATHAN MASSIGNANI\",\"15\",\"11\",\"11\"],[\"SM 2\",\"16\",\"SYLVAIN FOLLENFANT\",\"X\",\"15\",\"14\"],[\"SM 2\",\"74\",\"CEDRICK BOURGEOIS TURCOTTE\",\"X\",\"16\",\"12\"],[\"SM 2\",\"14\",\"FRANCOIS DUMONT LAQUERRE\",\"14\",\"X\",\"10\"],[\"SM 2\",\"38\",\"CHRIS BARTCZAK\",\"12\",\"10\",\"DNS\"],[\"SM 2\",\"717\",\"TOMMY PAQUETTE\",\"16\",\"DNS\",\"X\"],[\"SM 2\",\"144\",\"MICHAEL BOURASSA\",\"X\",\"X\",\"15\"],[\"SM 2\",\"177\",\"XAVIER MALTAIS\",\"X\",\"14\",\"X\"],[\"SM 2\",\"78\",\"CLAUDE SERGERIE\",\"DNF\",\"DNS\",\"DNS\"],[\"SM 2\",\"75\",\"FRANCK NUNES\",\"DNS\",\"X\",\"DNS\"],[\"SM 2\",\"11\",\"JOCELYN GAUTHIER\",\"X\",\"X\",\"DNS\"],[\"SM 2\",\"68\",\"JAGER STOCKILL\",\"X\",\"DNS\",\"X\"],[\"SM 3\",\"40\",\"BEN BELLEVILLE\",\"22\",\"22\",\"25\"],[\"SM 3\",\"10\",\"MARC DUMONT\",\"20\",\"20\",\"20\"],[\"SM 3\",\"1\",\"CHELSEA PARENT-BOURSIER\",\"25\",\"18\",\"10\"],[\"SM 3\",\"42\",\"DOMINIC ANDR\\u00c9-MONGEON\",\"X\",\"25\",\"22\"],[\"SM 3\",\"281\",\"VALERIAN RENAUD\",\"12\",\"16\",\"18\"],[\"SM 3\",\"97\",\"DYLAN SCOTT PAQUETTE\",\"11\",\"15\",\"13\"],[\"SM 3\",\"51\",\"MARTIN PARENT\",\"15\",\"13\",\"7\"],[\"SM 3\",\"145\",\"GABRIEL ST-PIERRE\",\"16\",\"11\",\"6\"],[\"SM 3\",\"29\",\"MARIO DUMAS\",\"13\",\"12\",\"8\"],[\"SM 3\",\"946\",\"ESTEBAN CANO\",\"9\",\"10\",\"12\"],[\"SM 3\",\"17\",\"SALOMON H\\u00c9RARD\",\"X\",\"14\",\"14\"],[\"SM 3\",\"404\",\"SHAWN ALEXIS GRONDIN GAUTHIER\",\"10\",\"9\",\"9\"],[\"SM 3\",\"11\",\"TOJO\",\"18\",\"DNS\",\"X\"],[\"SM 3\",\"504\",\"DAVID BOURSIER\",\"X\",\"X\",\"16\"],[\"SM 3\",\"11\",\"GABRIEL CHARETTE\",\"X\",\"X\",\"DNS\"],[\"SM 3\",\"450\",\"ALEXIS MARINEAU\",\"X\",\"DNS\",\"15\"],[\"SM 3\",\"16\",\"SYLVAIN FOLLENFANT\",\"14\",\"X\",\"X\"],[\"SM 3\",\"393\",\"RAPHA\\u00cbL LEMIRE\",\"X\",\"8\",\"5\"],[\"SM 3\",\"117\",\"ALEJANDRO RIVIERA\",\"X\",\"X\",\"11\"],[\"SM 3\",\"686\",\"FRED MONTMINY SAMSON\",\"DNS\",\"DNS\",\"DNS\"],[\"SM 3\",\"533\",\"MARTIN VILLENEUVE\",\"DNS\",\"DNS\",\"DNS\"],[\"SM 3\",\"3\",\"ANTOINE GAGNON\",\"DNF\",\"X\",\"DNS\"],[\"SM 3\",\"482\",\"KEWINE SEDRICK\",\"DNS\",\"DNS\",\"X\"],[\"SM 3\",\"99\",\"BENOIT DRAPEAU\",\"X\",\"DNS\",\"DNS\"],[\"SM 4\",\"321\",\"DANY MARCHAND\",\"22\",\"22\",\"25\"],[\"SM 4\",\"21\",\"XAVIER BELLEROPHON\",\"25\",\"25\",\"X\"],[\"SM 4\",\"78\",\"CLAUDE SERGERIE\",\"16\",\"20\",\"14\"],[\"SM 4\",\"9\",\"DIMITRI GODARD\",\"20\",\"DNS\",\"22\"],[\"SM 4\",\"292\",\"AUDREY FRAMAND\",\"14\",\"14\",\"13\"],[\"SM 4\",\"75\",\"FRANCK NUNES\",\"18\",\"X\",\"16\"],[\"SM 4\",\"38\",\"CHRIS BARTCZAK\",\"15\",\"15\",\"DNS\"],[\"SM 4\",\"341\",\"DAVID BERTRAND\",\"X\",\"X\",\"20\"],[\"SM 4\",\"281\",\"VALERIAN RENAUD\",\"X\",\"X\",\"18\"],[\"SM 4\",\"99\",\"BENOIT DRAPEAU\",\"X\",\"18\",\"X\"],[\"SM 4\",\"124\",\"ALEX LAROUCHE\",\"X\",\"16\",\"X\"],[\"SM 4\",\"213\",\"JONATHAN MASSIGNANI\",\"X\",\"X\",\"15\"],[\"SM 4\",\"29\",\"MARIO DUMAS\",\"X\",\"DNS\",\"DNS\"],[\"SM 4\",\"533\",\"MARTIN VILLENEUVE\",\"X\",\"DNS\",\"DNS\"],[\"SM 5\",\"33\",\"S\\u00c9BASTIEN LABELLE\",\"25\",\"25\",\"25\"],[\"SM 5\",\"35\",\"CL\\u00c9MENT LAROCHE\",\"X\",\"X\",\"22\"],[\"SM 5\",\"42\",\"MAUDE DESCH\\u00c8NES\",\"X\",\"DNS\",\"X\"],[\"SM 6\",\"1\",\"CHELSEA PARENT-BOURSIER\",\"25\",\"25\",\"25\"],[\"SM 6\",\"292\",\"AUDREY FRAMAND\",\"18\",\"20\",\"18\"],[\"SM 6\",\"31\",\"M\\u00c9LODIE H\\u00c9BERT BOURASSA\",\"X\",\"22\",\"22\"],[\"SM 6\",\"313\",\"SARAH MICHELLE COTTON\",\"22\",\"DNS\",\"20\"],[\"SM 6\",\"3\",\"ISABELLE G\\u00c9LINAS\",\"20\",\"18\",\"DNS\"],[\"SM 6\",\"22\",\"JULIE LALONDE\",\"16\",\"16\",\"DNS\"],[\"SM 6\",\"28\",\"NATHALIE VEZEAU\",\"15\",\"DNS\",\"X\"],[\"SM 6\",\"42\",\"MAUDE DESCH\\u00c8NES\",\"DNS\",\"DNS\",\"X\"],[\"SM 7\",\"1\",\"CHELSEA PARENT-BOURSIER\",\"25\",\"25\",\"25\"],[\"SM 7\",\"21\",\"MAXIME BELLEROPHON\",\"22\",\"20\",\"X\"],[\"SM 7\",\"42\",\"JAYSON MONGEON\",\"X\",\"X\",\"22\"],[\"SM 7\",\"68\",\"JAGER STOCKILL\",\"X\",\"22\",\"X\"],[\"SM 8\",\"66\",\"RYAN MORIN\",\"22\",\"20\",\"22\"],[\"SM 8\",\"1\",\"OCTAVIA PINEL BOURSIER\",\"20\",\"18\",\"20\"],[\"SM 8\",\"21\",\"MAXIME BELLEROPHON\",\"25\",\"25\",\"X\"],[\"SM 8\",\"42\",\"JAYSON MONGEON\",\"X\",\"22\",\"25\"],[\"SM 8\",\"5\",\"JAYDEN VALL\\u00c9E\",\"18\",\"X\",\"15\"],[\"SM 8\",\"905\",\"ABBY DUBUC\",\"DNF\",\"X\",\"18\"],[\"SM 8\",\"11\",\"MAGGIE BELLEVILLE\",\"X\",\"X\",\"16\"],[\"SM 8\",\"244\",\"CHARLES MALTAIS\",\"X\",\"16\",\"X\"],[\"SM 8\",\"117\",\"LOIC MALTAIS\",\"X\",\"DNF\",\"X\"],[\"SM 9\",\"1\",\"CHELSEA PARENT-BOURSIER\",\"25\",\"25\",\"25\"],[\"SM 9\",\"22\",\"JULIE LALONDE\",\"16\",\"14\",\"20\"],[\"SM 9\",\"313\",\"SARAH MICHELLE COTTON\",\"22\",\"22\",\"DNS\"],[\"SM 9\",\"31\",\"M\\u00c9LODIE H\\u00c9BERT BOURASSA\",\"X\",\"16\",\"22\"],[\"SM 9\",\"3\",\"ISABELLE G\\u00c9LINAS\",\"20\",\"18\",\"DNS\"],[\"SM 9\",\"439\",\"JULIE SIMARD\",\"15\",\"20\",\"DNS\"],[\"SM 9\",\"292\",\"AUDREY FRAMAND\",\"14\",\"13\",\"DNS\"],[\"SM 9\",\"77\",\"LISA CHABOT\",\"18\",\"X\",\"DNS\"],[\"SM 9\",\"42\",\"MAUDE DESCH\\u00c8NES\",\"X\",\"15\",\"X\"],[\"SM 9\",\"28\",\"NATHALIE VEZEAU\",\"13\",\"DNS\",\"X\"]],\"CSM - 2026\":[[\"CATEGORY\",\"#\",\"NAME\",\"SHANNONVILLE\",\"MECAGLISSE\",\"SKRDL\",\"GP3R-S\",\"GP3R-D\",\"LOMBARDY\",\"KTR\"],[\"SM 1\",\"21\",\"XAVIER BELLEROPHON\",\"22\",\"22\",\"25\",\"25\",\"18\",\"25\",\"25\"],[\"SM 1\",\"1\",\"NELSON JR MOREAU\",\"25\",\"25\",\"12\",\"22\",\"25\",\"X\",\"X\"],[\"SM 1\",\"654\",\"JACOB BILODEAU\",\"12\",\"18\",\"20\",\"20\",\"15\",\"18\",\"20\"],[\"SM 1\",\"93\",\"ALEX DESHAIES\",\"14\",\"20\",\"22\",\"13\",\"11\",\"13\",\"18\"],[\"SM 1\",\"104\",\"MAVRICK CYR\",\"X\",\"X\",\"14\",\"16\",\"20\",\"20\",\"22\"],[\"SM 1\",\"24\",\"S\\u00c9BASTIEN TREMBLAY\",\"20\",\"DNF\",\"DNS\",\"8\",\"16\",\"22\",\"16\"],[\"SM 1\",\"67\",\"JIMMY MORIN\",\"X\",\"14\",\"11\",\"12\",\"9\",\"16\",\"15\"],[\"SM 1\",\"44\",\"GABRIEL VALL\\u00c9E\",\"X\",\"X\",\"X\",\"18\",\"22\",\"15\",\"X\"],[\"SM 1\",\"9\",\"DIMITRI GODARD\",\"11\",\"X\",\"DNS\",\"10\",\"12\",\"14\",\"DNS\"],[\"SM 1\",\"777\",\"ZAIM LAFLAMME\",\"18\",\"12\",\"16\",\"X\",\"X\",\"X\",\"X\"],[\"SM 1\",\"87\",\"DAVID GUERTIN\",\"9\",\"DNS\",\"18\",\"11\",\"8\",\"X\",\"DNS\"],[\"SM 1\",\"31\",\"PAUL-\\u00c9TIENNE COURTOIS\",\"16\",\"15\",\"X\",\"7\",\"7\",\"X\",\"X\"],[\"SM 1\",\"94\",\"LUDOVICK S\\u00c9VIGNY\",\"13\",\"X\",\"15\",\"X\",\"X\",\"X\",\"X\"],[\"SM 1\",\"73\",\"MICHAEL GAGNON\",\"X\",\"X\",\"X\",\"14\",\"13\",\"X\",\"X\"],[\"SM 1\",\"341\",\"DAVID BERTRAND\",\"X\",\"X\",\"X\",\"15\",\"10\",\"X\",\"X\"],[\"SM 1\",\"3\",\"MARTIN CARPENTIER\",\"8\",\"16\",\"X\",\"DNS\",\"DNS\",\"X\",\"X\"],[\"SM 1\",\"79\",\"MACKENZIE WEIL\",\"10\",\"X\",\"13\",\"X\",\"X\",\"X\",\"X\"],[\"SM 1\",\"88\",\"THOMAS CASAS\",\"15\",\"X\",\"X\",\"X\",\"X\",\"X\",\"X\"],[\"SM 1\",\"183\",\"DAVID POTHIER\",\"X\",\"X\",\"X\",\"DNF\",\"14\",\"X\",\"X\"],[\"SM 1\",\"151\",\"JC BERNIER\",\"X\",\"13\",\"X\",\"X\",\"X\",\"X\",\"X\"],[\"SM 1\",\"112\",\"NICOLAS MEUNIER\",\"X\",\"X\",\"X\",\"9\",\"DNF\",\"X\",\"X\"],[\"SM 1\",\"34\",\"DAVE AUGER\",\"DNS\",\"X\",\"DNS\",\"X\",\"X\",\"X\",\"X\"],[\"SM 1\",\"X\",\"VINCENT WILSON\",\"DNS\",\"X\",\"X\",\"X\",\"X\",\"X\",\"X\"],[\"SM 2\",\"158\",\"THOMAS LAROUCHE\",\"25\",\"22\",\"20\",\"N\\\/A\",\"N\\\/A\",\"16\",\"22\"],[\"SM 2\",\"7\",\"THOMAS MANSEAU\",\"18\",\"25\",\"12\",\"N\\\/A\",\"N\\\/A\",\"25\",\"20\"],[\"SM 2\",\"27\",\"JEREMIE LAUZI\\u00c8RE\",\"22\",\"12\",\"18\",\"N\\\/A\",\"N\\\/A\",\"20\",\"16\"],[\"SM 2\",\"229\",\"ANTHONY GOSSELIN\",\"20\",\"16\",\"13\",\"N\\\/A\",\"N\\\/A\",\"22\",\"14\"],[\"SM 2\",\"177\",\"XAVIER MALTAIS\",\"10\",\"18\",\"14\",\"N\\\/A\",\"N\\\/A\",\"13\",\"X\"],[\"SM 2\",\"213\",\"JONATHAN MASSIGNANI\",\"15\",\"14\",\"11\",\"N\\\/A\",\"N\\\/A\",\"15\",\"11\"],[\"SM 2\",\"74\",\"CEDRICK BOURGEOIS TURCOTTE\",\"13\",\"10\",\"16\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 2\",\"38\",\"CHRIS BARTCZAK\",\"14\",\"15\",\"10\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 2\",\"13\",\"ROMAIN LANGLET\",\"X\",\"X\",\"22\",\"N\\\/A\",\"N\\\/A\",\"14\",\"15\"],[\"SM 2\",\"68\",\"JAGER STOCKILL\",\"DNS\",\"13\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"18\",\"25\"],[\"SM 2\",\"14\",\"FRANCOIS DUMONT LAQUERRE\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"12\"],[\"SM 2\",\"16\",\"SYLVAIN FOLLENFANT\",\"X\",\"X\",\"15\",\"N\\\/A\",\"N\\\/A\",\"12\",\"X\"],[\"SM 2\",\"321\",\"DANY MARCHAND\",\"X\",\"X\",\"25\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 2\",\"77\",\"ADRIEN JACQUART\",\"11\",\"11\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"13\"],[\"SM 2\",\"75\",\"FRANCK NUNES\",\"DNS\",\"20\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 2\",\"717\",\"TOMMY PAQUETTE\",\"16\",\"DNS\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 2\",\"144\",\"MICHAEL BOURASSA\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"18\"],[\"SM 2\",\"78\",\"CLAUDE SERGERIE\",\"12\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"X\"],[\"SM 3\",\"10\",\"MARC DUMONT\",\"20\",\"22\",\"20\",\"N\\\/A\",\"N\\\/A\",\"22\",\"15\"],[\"SM 3\",\"117\",\"ALEJANDRO RIVIERA\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"16\"],[\"SM 3\",\"1\",\"CHELSEA PARENT-BOURSIER\",\"18\",\"25\",\"18\",\"N\\\/A\",\"N\\\/A\",\"15\",\"13\"],[\"SM 3\",\"42\",\"DOMINIC ANDR\\u00c9-MONGEON\",\"X\",\"18\",\"25\",\"N\\\/A\",\"N\\\/A\",\"25\",\"22\"],[\"SM 3\",\"42\",\"PHILIPPE LUSIGNAN\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"DNS\"],[\"SM 3\",\"5\",\"PHILIPPE BOURDEAU\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"DNS\"],[\"SM 3\",\"281\",\"VALERIAN RENAUD\",\"13\",\"13\",\"16\",\"N\\\/A\",\"N\\\/A\",\"18\",\"14\"],[\"SM 3\",\"40\",\"BEN BELLEVILLE\",\"15\",\"20\",\"22\",\"N\\\/A\",\"N\\\/A\",\"X\",\"25\"],[\"SM 3\",\"97\",\"DYLAN SCOTT PAQUETTE\",\"12\",\"14\",\"15\",\"N\\\/A\",\"N\\\/A\",\"10\",\"9\"],[\"SM 3\",\"51\",\"MARTIN PARENT\",\"16\",\"11\",\"13\",\"N\\\/A\",\"N\\\/A\",\"9\",\"11\"],[\"SM 3\",\"404\",\"SHAWN ALEXIS GRONDIN GAUTHIER\",\"10\",\"15\",\"9\",\"N\\\/A\",\"N\\\/A\",\"14\",\"DNS\"],[\"SM 3\",\"17\",\"SALOMON H\\u00c9RARD\",\"X\",\"X\",\"14\",\"N\\\/A\",\"N\\\/A\",\"20\",\"18\"],[\"SM 3\",\"145\",\"GABRIEL ST-PIERRE\",\"8\",\"8\",\"11\",\"N\\\/A\",\"N\\\/A\",\"X\",\"8\"],[\"SM 3\",\"321\",\"DANY MARCHAND\",\"25\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 3\",\"16\",\"SYLVAIN FOLLENFANT\",\"22\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 3\",\"946\",\"ESTEBAN CANO\",\"X\",\"10\",\"10\",\"N\\\/A\",\"N\\\/A\",\"X\",\"12\"],[\"SM 3\",\"393\",\"RAPHA\\u00cbL LEMIRE\",\"11\",\"X\",\"8\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 3\",\"70\",\"GUILLAUME REEVES VACHON\",\"9\",\"9\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 3\",\"450\",\"ALEXIS MARINEAU\",\"DNS\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"16\",\"20\"],[\"SM 3\",\"104\",\"MAVRICK CYR\",\"X\",\"16\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 3\",\"11\",\"TOJO\",\"14\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 3\",\"248\",\"JULIEN GALARNEAU\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"13\",\"X\"],[\"SM 3\",\"99\",\"BENOIT DRAPEAU\",\"X\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"12\",\"X\"],[\"SM 3\",\"29\",\"MARIO DUMAS\",\"DNS\",\"X\",\"12\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"DNS\"],[\"SM 3\",\"33\",\"S\\u00c9BASTIEN LABELLE\",\"X\",\"12\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 3\",\"78\",\"JULIEN DUBOIS\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"11\",\"10\"],[\"SM 3\",\"21\",\"MAXIME BELLEROPHON\",\"X\",\"7\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 3\",\"85\",\"BEN GRAHAM\",\"7\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 3\",\"330\",\"ANTOINE GAGNON\",\"6\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 3\",\"533\",\"MARTIN VILLENEUVE\",\"DNS\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"DNS\"],[\"SM 3\",\"686\",\"FRED MONTMINY SAMSON\",\"X\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"DNS\"],[\"SM 3\",\"482\",\"KEWINE SEDRICK\",\"X\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 3\",\"26\",\"ARNAUD BOURCIER\",\"DNS\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 3\",\"323\",\"PIERRE JUNIOR CHARRETTE\",\"DNS\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 4\",\"21\",\"XAVIER BELLEROPHON\",\"25\",\"25\",\"25\",\"N\\\/A\",\"N\\\/A\",\"25\",\"25\"],[\"SM 4\",\"38\",\"CHRIS BARTCZAK\",\"13\",\"16\",\"15\",\"N\\\/A\",\"N\\\/A\",\"18\",\"16\"],[\"SM 4\",\"78\",\"CLAUDE SERGERIE\",\"20\",\"X\",\"20\",\"N\\\/A\",\"N\\\/A\",\"16\",\"18\"],[\"SM 4\",\"321\",\"DANY MARCHAND\",\"22\",\"X\",\"22\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 4\",\"292\",\"AUDREY FRAMAND\",\"14\",\"15\",\"14\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"X\"],[\"SM 4\",\"75\",\"FRANCK NUNES\",\"16\",\"22\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"DNS\"],[\"SM 4\",\"29\",\"MARIO DUMAS\",\"15\",\"14\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"DNF\"],[\"SM 4\",\"9\",\"DIMITRI GODARD\",\"DNS\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"22\",\"22\"],[\"SM 4\",\"213\",\"JONATHAN MASSIGNANI\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"20\",\"15\"],[\"SM 4\",\"341\",\"DAVID BERTRAND\",\"X\",\"20\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 4\",\"99\",\"BENOIT DRAPEAU\",\"X\",\"X\",\"18\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 4\",\"500\",\"PATRICE LABRECQUE\",\"X\",\"18\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 4\",\"51\",\"MARTIN PARENT\",\"18\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 4\",\"124\",\"ALEX LAROUCHE\",\"X\",\"X\",\"16\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 4\",\"281\",\"VALERIAN RENAUD\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"15\",\"20\"],[\"SM 4\",\"533\",\"MARTIN VILLENEUVE\",\"X\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"X\"],[\"SM 4\",\"323\",\"PIERRE JUNIOR CHARRETTE\",\"DNS\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 4\",\"24\",\"YANNICK LAFLAMME\",\"DNS\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 5\",\"33\",\"S\\u00c9BASTIEN LABELLE\",\"25\",\"25\",\"25\",\"N\\\/A\",\"N\\\/A\",\"25\",\"DNF\"],[\"SM 5\",\"367\",\"MARIO BERGERON\",\"22\",\"22\",\"X\",\"N\\\/A\",\"N\\\/A\",\"20\",\"X\"],[\"SM 5\",\"35\",\"CL\\u00c9MENT LAROCHE\",\"DNF\",\"20\",\"X\",\"N\\\/A\",\"N\\\/A\",\"22\",\"25\"],[\"SM 5\",\"42\",\"MAUDE DESCH\\u00c8NES\",\"X\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 5\",\"\",\"ARNAUD BOURCIER\",\"DNS\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 5\",\"29\",\"MARIO DUMAS\",\"DNS\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 6\",\"1\",\"CHELSEA PARENT-BOURSIER\",\"25\",\"25\",\"25\",\"N\\\/A\",\"N\\\/A\",\"25\",\"DNS\"],[\"SM 6\",\"22\",\"JULIE LALONDE\",\"15\",\"16\",\"16\",\"N\\\/A\",\"N\\\/A\",\"20\",\"18\"],[\"SM 6\",\"31\",\"M\\u00c9LODIE H\\u00c9BERT BOURASSA\",\"22\",\"18\",\"22\",\"N\\\/A\",\"N\\\/A\",\"X\",\"25\"],[\"SM 6\",\"3\",\"ISABELLE G\\u00c9LINAS\",\"16\",\"22\",\"18\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 6\",\"292\",\"AUDREY FRAMAND\",\"13\",\"20\",\"20\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"X\"],[\"SM 6\",\"28\",\"NATHALIE VEZEAU\",\"14\",\"15\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 6\",\"X\",\"CLAUDINE OUELLET\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"22\"],[\"SM 6\",\"654\",\"JULIE SIMARD\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"22\",\"X\"],[\"SM 6\",\"313\",\"SARAH MICHELLE COTTON\",\"20\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"20\"],[\"SM 6\",\"77\",\"LISA CHABOT\",\"18\",\"DNS\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 6\",\"222\",\"MARIE-CLAUDE PARENT\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"X\"],[\"SM 6\",\"42\",\"MAUDE DESCH\\u00c8NES\",\"DNS\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"X\"],[\"SM 6\",\"20\",\"KIM ISABELLE\",\"DNS\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 7\",\"2\",\"CHELSEA PARENT-BOURSIER\",\"DNS\",\"25\",\"25\",\"N\\\/A\",\"N\\\/A\",\"25\",\"X\"],[\"SM 7\",\"21\",\"MAXIME BELLEROPHON\",\"X\",\"22\",\"20\",\"N\\\/A\",\"N\\\/A\",\"18\",\"18\"],[\"SM 7\",\"17\",\"SALOMON H\\u00c9RARD\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"22\",\"22\"],[\"SM 7\",\"68\",\"JAGER STOCKILL\",\"DNS\",\"X\",\"22\",\"N\\\/A\",\"N\\\/A\",\"X\",\"25\"],[\"SM 7\",\"78\",\"JULIEN DUBOIS\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"20\",\"20\"],[\"SM 7\",\"42\",\"JAYSON MONGEON\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"16\",\"X\"],[\"SM 7\",\"124\",\"THOMAS LAROUCHE\",\"DNS\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 8\",\"21\",\"MAXIME BELLEROPHON\",\"25\",\"25\",\"25\",\"N\\\/A\",\"N\\\/A\",\"25\",\"25\"],[\"SM 8\",\"42\",\"JAYSON MONGEON\",\"X\",\"22\",\"22\",\"N\\\/A\",\"N\\\/A\",\"22\",\"22\"],[\"SM 8\",\"244\",\"CHARLES MALTAIS\",\"14\",\"15\",\"16\",\"N\\\/A\",\"N\\\/A\",\"16\",\"X\"],[\"SM 8\",\"66\",\"RYAN MORIN\",\"X\",\"20\",\"20\",\"N\\\/A\",\"N\\\/A\",\"20\",\"18\"],[\"SM 8\",\"1\",\"OCTAVIA PINEL BOURSIER\",\"22\",\"18\",\"18\",\"N\\\/A\",\"N\\\/A\",\"X\",\"DNS\"],[\"SM 8\",\"117\",\"LOIC MALTAIS\",\"15\",\"16\",\"DNF\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"X\"],[\"SM 8\",\"11\",\"ISAAC DUPUIS\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"16\"],[\"SM 8\",\"913\",\"ABEL BYBERG\",\"20\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 8\",\"5\",\"JAYDEN VALL\\u00c9E\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"18\",\"X\"],[\"SM 8\",\"3\",\"CROSBY BYBERG\",\"18\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 8\",\"89\",\"COLE HODGSON\",\"16\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 8\",\"905\",\"ABBY DUBUC\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"20\"],[\"SM 8\",\"19\",\"EVERLY LANDRY\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"X\"],[\"SM 9\",\"1\",\"CHELSEA PARENT-BOURSIER\",\"25\",\"25\",\"25\",\"N\\\/A\",\"N\\\/A\",\"25\",\"25\"],[\"SM 9\",\"439\",\"JULIE SIMARD\",\"20\",\"22\",\"20\",\"N\\\/A\",\"N\\\/A\",\"22\",\"14\"],[\"SM 9\",\"22\",\"JULIE LALONDE\",\"15\",\"15\",\"14\",\"N\\\/A\",\"N\\\/A\",\"18\",\"16\"],[\"SM 9\",\"31\",\"M\\u00c9LODIE H\\u00c9BERT BOURASSA\",\"22\",\"18\",\"16\",\"N\\\/A\",\"N\\\/A\",\"X\",\"22\"],[\"SM 9\",\"3\",\"ISABELLE G\\u00c9LINAS\",\"16\",\"16\",\"18\",\"N\\\/A\",\"N\\\/A\",\"X\",\"20\"],[\"SM 9\",\"313\",\"SARAH MICHELLE COTTON\",\"18\",\"X\",\"22\",\"N\\\/A\",\"N\\\/A\",\"X\",\"15\"],[\"SM 9\",\"292\",\"AUDREY FRAMAND\",\"13\",\"14\",\"13\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"12\"],[\"SM 9\",\"28\",\"NATHALIE VEZEAU\",\"14\",\"13\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 9\",\"27\",\"GABRIELLE FUG\\u00c8RE\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"20\",\"18\"],[\"SM 9\",\"77\",\"LISA CHABOT\",\"DNS\",\"20\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 9\",\"33\",\"MARTINE BUSSI\\u00c8RE\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"16\",\"13\"],[\"SM 9\",\"1x\",\"MARIE-CLAUDE PARENT\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"15\",\"X\"],[\"SM 9\",\"42\",\"MAUDE DESCH\\u00c8NES\",\"DNS\",\"X\",\"15\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"X\"],[\"SM 9\",\"67\",\"ALLISON LIVERNOCHE\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"X\"],[\"SM 9\",\"99\",\"EMILY DICKSON\",\"DNS\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 9\",\"20\",\"KIM ISABELLE\",\"DNS\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"10\",\"MARC DUMONT\",\"16\",\"25\",\"25\",\"N\\\/A\",\"N\\\/A\",\"22\",\"22\"],[\"SM 10\",\"450\",\"ALEXIS MARINEAU\",\"12\",\"X\",\"20\",\"N\\\/A\",\"N\\\/A\",\"25\",\"18\"],[\"SM 10\",\"404\",\"SHAWN ALEXIS GRONDIN GAUTHIER\",\"6\",\"16\",\"10\",\"N\\\/A\",\"N\\\/A\",\"20\",\"20\"],[\"SM 10\",\"405\",\"ERIC BELLEMARE\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"10\"],[\"SM 10\",\"1\",\"CHELSEA PARENT-BOURSIER\",\"11\",\"11\",\"11\",\"N\\\/A\",\"N\\\/A\",\"18\",\"25\"],[\"SM 10\",\"281\",\"VALERIAN RENAUD\",\"7\",\"15\",\"14\",\"N\\\/A\",\"N\\\/A\",\"14\",\"12\"],[\"SM 10\",\"511\",\"SEBASTIEN BOUCHARD\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"5\"],[\"SM 10\",\"51\",\"MARTIN PARENT\",\"15\",\"20\",\"12\",\"N\\\/A\",\"N\\\/A\",\"X\",\"11\"],[\"SM 10\",\"11\",\"TOJO\",\"20\",\"X\",\"18\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"145\",\"GABRIEL ST-PIERRE\",\"1\",\"13\",\"22\",\"N\\\/A\",\"N\\\/A\",\"X\",\"13\"],[\"SM 10\",\"330\",\"ANTOINE GAGNON\",\"10\",\"22\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"29\",\"MARIO DUMAS\",\"DNS\",\"14\",\"16\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"DNF\"],[\"SM 10\",\"99\",\"BENOIT DRAPEAU\",\"X\",\"X\",\"13\",\"N\\\/A\",\"N\\\/A\",\"16\",\"16\"],[\"SM 10\",\"59\",\"CLAUDE DENONCOURT\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"DNS\"],[\"SM 10\",\"482\",\"KEWINE SEDRICK\",\"14\",\"X\",\"15\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"42\",\"DOMINIC ANDR\\u00c9-MONGEON\",\"X\",\"9\",\"6\",\"N\\\/A\",\"N\\\/A\",\"13\",\"15\"],[\"SM 10\",\"321\",\"DANY MARCHAND\",\"25\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"97\",\"DYLAN SCOTT PAQUETTE\",\"13\",\"1\",\"9\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"4\"],[\"SM 10\",\"40\",\"BEN BELLEVILLE\",\"22\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"17\",\"SALOMON H\\u00c9RARD\",\"X\",\"X\",\"7\",\"N\\\/A\",\"N\\\/A\",\"15\",\"9\"],[\"SM 10\",\"35\",\"CL\\u00c9MENT LAROCHE\",\"3\",\"8\",\"X\",\"N\\\/A\",\"N\\\/A\",\"11\",\"X\"],[\"SM 10\",\"33\",\"S\\u00c9BASTIEN LABELLE\",\"5\",\"3\",\"4\",\"N\\\/A\",\"N\\\/A\",\"10\",\"2\"],[\"SM 10\",\"70\",\"GUILLAUME REEVES VACHON\",\"9\",\"12\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"45\",\"MIGUEL GAUDREAULT\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"14\"],[\"SM 10\",\"197\",\"KEVIN PLOUFFE\",\"X\",\"18\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"16\",\"SYLVAIN FOLLENFANT\",\"18\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"78\",\"JULIEN DUBOIS\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"12\",\"3\"],[\"SM 10\",\"15\",\"ESTEBAN CANO\",\"8\",\"4\",\"0\",\"N\\\/A\",\"N\\\/A\",\"X\",\"8\"],[\"SM 10\",\"922\",\"JEAN-PHILIPPE AUDET HALL\",\"DNS\",\"6\",\"5\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"DNS\"],[\"SM 10\",\"23\",\"FELIX COTE\",\"X\",\"10\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"24\",\"YANNICK LAFLAMME\",\"DNS\",\"7\",\"2\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"393\",\"RAPHA\\u00cbL LEMIRE\",\"X\",\"X\",\"8\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"500\",\"PATRICE LABRECQUE\",\"X\",\"5\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"717\",\"TOMMY PAQUETTE\",\"4\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"313\",\"SARAH MICHELLE COTTON\",\"0\",\"X\",\"3\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"533\",\"MARTIN VILLENEUVE\",\"DNS\",\"2\",\"0\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"1\"],[\"SM 10\",\"5\",\"PHILIPPE BOURDEAU\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"6\"],[\"SM 10\",\"5\",\"MAX BROYDA\",\"2\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"117\",\"ALEJANDRO RIVIERA\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"7\"],[\"SM 10\",\"686\",\"FRED MONTMINY SAMSON\",\"X\",\"X\",\"1\",\"N\\\/A\",\"N\\\/A\",\"X\",\"DNS\"],[\"SM 10\",\"292\",\"FR\\u00c9D\\u00c9RIC GAUTHIER DOYLE\",\"0\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"26\",\"ARNAUD BOURCIER\",\"DNS\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"85\",\"BEN GRAHAM\",\"DNS\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"84\",\"BENJAMIN HODGSON\",\"DNS\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"140\",\"FRANCIS VALOIS\",\"DNS\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"325\",\"PIERRE JUNIOR CHARRETTE\",\"DNS\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 11\",\"7\",\"THOMAS MANSEAU\",\"20\",\"25\",\"18\",\"N\\\/A\",\"N\\\/A\",\"22\",\"18\"],[\"SM 11\",\"27\",\"JEREMIE LAUZI\\u00c8RE\",\"25\",\"18\",\"25\",\"N\\\/A\",\"N\\\/A\",\"16\",\"20\"],[\"SM 11\",\"229\",\"ANTHONY GOSSELIN\",\"16\",\"22\",\"22\",\"N\\\/A\",\"N\\\/A\",\"18\",\"25\"],[\"SM 11\",\"16\",\"SYLVAIN FOLLENFANT\",\"X\",\"20\",\"20\",\"N\\\/A\",\"N\\\/A\",\"20\",\"16\"],[\"SM 11\",\"213\",\"JONATHAN MASSIGNANI\",\"14\",\"16\",\"14\",\"N\\\/A\",\"N\\\/A\",\"15\",\"15\"],[\"SM 11\",\"14\",\"FRANCOIS DUMONT LAQUERRE\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"9\"],[\"SM 11\",\"177\",\"XAVIER MALTAIS\",\"8\",\"13\",\"12\",\"N\\\/A\",\"N\\\/A\",\"13\",\"X\"],[\"SM 11\",\"13\",\"ROMAIN LANGLET\",\"X\",\"X\",\"15\",\"N\\\/A\",\"N\\\/A\",\"25\",\"14\"],[\"SM 11\",\"158\",\"THOMAS LAROUCHE\",\"13\",\"14\",\"13\",\"N\\\/A\",\"N\\\/A\",\"X\",\"13\"],[\"SM 11\",\"75\",\"FRANCK NUNES\",\"22\",\"15\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 11\",\"38\",\"CHRIS BARTCZAK\",\"11\",\"12\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"12\",\"12\"],[\"SM 11\",\"717\",\"TOMMY PAQUETTE\",\"18\",\"DNS\",\"11\",\"N\\\/A\",\"N\\\/A\",\"X\",\"10\"],[\"SM 11\",\"74\",\"CEDRICK BOURGEOIS TURCOTTE\",\"15\",\"10\",\"DNF\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 11\",\"68\",\"JAGER STOCKILL\",\"DNS\",\"DNS\",\"10\",\"N\\\/A\",\"N\\\/A\",\"14\",\"22\"],[\"SM 11\",\"78\",\"CLAUDE SERGERIE\",\"12\",\"X\",\"9\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"11\"],[\"SM 11\",\"77\",\"ADRIEN JACQUART\",\"10\",\"11\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"8\"],[\"SM 11\",\"321\",\"DANY MARCHAND\",\"X\",\"X\",\"16\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 11\",\"88R\",\"LAURENT LALIBERT\\u00c9 GIRARD\",\"9\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 12\",\"24\",\"S\\u00c9BASTIEN TREMBLAY\",\"22\",\"25\",\"18\",\"N\\\/A\",\"N\\\/A\",\"25\",\"22\"],[\"SM 12\",\"93\",\"ALEX DESHAIES\",\"15\",\"20\",\"20\",\"N\\\/A\",\"N\\\/A\",\"22\",\"25\"],[\"SM 12\",\"1\",\"NELSON JR MOREAU\",\"25\",\"22\",\"25\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 12\",\"21\",\"XAVIER BELLEROPHON\",\"14\",\"18\",\"16\",\"N\\\/A\",\"N\\\/A\",\"16\",\"16\"],[\"SM 12\",\"777\",\"ZAIM LAFLAMME\",\"18\",\"16\",\"22\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 12\",\"9\",\"DIMITRI GODARD\",\"16\",\"X\",\"15\",\"N\\\/A\",\"N\\\/A\",\"18\",\"18\"],[\"SM 12\",\"67\",\"JIMMY MORIN\",\"X\",\"14\",\"12\",\"N\\\/A\",\"N\\\/A\",\"15\",\"X\"],[\"SM 12\",\"31\",\"PAUL-\\u00c9TIENNE COURTOIS\",\"20\",\"15\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 12\",\"104\",\"MAVRICK CYR\",\"X\",\"X\",\"14\",\"N\\\/A\",\"N\\\/A\",\"20\",\"20\"],[\"SM 12\",\"654\",\"JACOB BILODEAU\",\"13\",\"X\",\"13\",\"N\\\/A\",\"N\\\/A\",\"X\",\"15\"],[\"SM 12\",\"94\",\"LUDOVICK S\\u00c9VIGNY\",\"12\",\"X\",\"11\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 12\",\"87\",\"DAVID GUERTIN\",\"11\",\"X\",\"9\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 12\",\"807\",\"GABRIEL VALL\\u00c9E\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"14\",\"X\"],[\"SM 12\",\"79\",\"MACKENZIE WEIL\",\"DNS\",\"X\",\"10\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 12\",\"88\",\"VINCENT WILSON\",\"10\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 12\",\"3\",\"MARTIN CARPENTIER\",\"9\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 12\",\"34\",\"DAVE AUGER\",\"DNS\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"LW\",\"222\",\"CHELSEA PARENT-BOURSIER\",\"20\",\"22\",\"22\",\"N\\\/A\",\"N\\\/A\",\"22\",\"22\"],[\"LW\",\"22\",\"ZAIM LAFLAMME\",\"18\",\"25\",\"25\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"LW\",\"24\",\"YANNICK LAFLAMME\",\"13\",\"18\",\"15\",\"N\\\/A\",\"N\\\/A\",\"18\",\"15\"],[\"LW\",\"922\",\"JEAN-PHILIPPE AUDET HALL\",\"14\",\"16\",\"18\",\"N\\\/A\",\"N\\\/A\",\"15\",\"16\"],[\"LW\",\"145\",\"GABRIEL ST-PIERRE\",\"16\",\"15\",\"16\",\"N\\\/A\",\"N\\\/A\",\"X\",\"20\"],[\"LW\",\"717\",\"TOMMY PAQUETTE\",\"22\",\"DNS\",\"20\",\"N\\\/A\",\"N\\\/A\",\"X\",\"25\"],[\"LW\",\"29\",\"AUDREY FRAMAND\",\"8\",\"12\",\"13\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"LW\",\"439\",\"JULIE SIMARD\",\"12\",\"DNS\",\"14\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"DNF\"],[\"LW\",\"248\",\"JULIEN GALARNEAU\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"25\",\"X\"],[\"LW\",\"313\",\"DIMITRI GODARD\",\"25\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"LW\",\"292\",\"FR\\u00c9D\\u00c9RIC GAUTHIER DOYLE\",\"9\",\"14\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"LW\",\"22\",\"MAVRICK CYR\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"20\",\"DNS\"],[\"LW\",\"95\",\"ALEXIS BAHL\",\"X\",\"20\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"LW\",\"760\",\"WILLIAM GENEST\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"16\",\"X\"],[\"LW\",\"49\",\"BENOIT B\\u00c9LAIR\",\"15\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"18\"],[\"LW\",\"27\",\"GABRIELLE FUG\\u00c8RE\",\"X\",\"13\",\"X\",\"N\\\/A\",\"N\\\/A\",\"DNF\",\"14\"],[\"LW\",\"21\",\"MAXIME BELLEROPHON\",\"11\",\"DNS\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"LW\",\"99\",\"EMILY DICKSON\",\"10\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"LW\",\"34\",\"DAVE AUGER\",\"X\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"LW\",\"52\",\"THIBAULT GILBERT\",\"X\",\"DNS\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"LW\",\"222V\",\"AARON MEAD\",\"DNF\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"MINI MOTO 1\",\"213\",\"JONATHAN MASSIGNANI\",\"22\",\"N\\\/A\",\"25\",\"N\\\/A\",\"N\\\/A\",\"22\",\"22\"],[\"MINI MOTO 1\",\"16\",\"SYLVAIN FOLLENFANT\",\"25\",\"N\\\/A\",\"22\",\"N\\\/A\",\"N\\\/A\",\"X\",\"20\"],[\"MINI MOTO 1\",\"51\",\"MARTIN PARENT\",\"18\",\"N\\\/A\",\"20\",\"N\\\/A\",\"N\\\/A\",\"X\",\"16\"],[\"MINI MOTO 1\",\"42\",\"PHILIPPE LUSIGNAN\",\"X\",\"N\\\/A\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"18\"],[\"MINI MOTO 1\",\"68\",\"JAGER STOCKILL\",\"DNS\",\"N\\\/A\",\"DNF\",\"N\\\/A\",\"N\\\/A\",\"25\",\"25\"],[\"MINI MOTO 1\",\"248\",\"JULIEN GALARNEAU\",\"X\",\"N\\\/A\",\"X\",\"N\\\/A\",\"N\\\/A\",\"20\",\"X\"],[\"MINI MOTO 1\",\"84\",\"BENJAMIN HODGSON\",\"20\",\"N\\\/A\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"MINI MOTO 2\",\"222\",\"CHELSEA PARENT-BOURSIER\",\"25\",\"N\\\/A\",\"25\",\"N\\\/A\",\"N\\\/A\",\"25\",\"20\"],[\"MINI MOTO 2\",\"21\",\"MAXIME BELLEROPHON\",\"22\",\"N\\\/A\",\"22\",\"N\\\/A\",\"N\\\/A\",\"20\",\"18\"],[\"MINI MOTO 2\",\"5\",\"PHILIPPE BOURDEAU\",\"X\",\"N\\\/A\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"25\"],[\"MINI MOTO 2\",\"149\",\"PATRICK PRATTE\",\"X\",\"N\\\/A\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"22\"],[\"MINI MOTO 3\",\"905\",\"ABBY DUBUC\",\"X\",\"N\\\/A\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"18\"],[\"MINI MOTO 2\",\"22\",\"JULIE LALONDE\",\"16\",\"N\\\/A\",\"20\",\"N\\\/A\",\"N\\\/A\",\"22\",\"16\"],[\"MINI MOTO 2\",\"85\",\"BEN GRAHAM\",\"20\",\"N\\\/A\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"MINI MOTO 2\",\"313\",\"SARAH MICHELLE COTTON\",\"X\",\"N\\\/A\",\"18\",\"N\\\/A\",\"N\\\/A\",\"X\",\"15\"],[\"MINI MOTO 2\",\"27\",\"COHEN BYBERG\",\"18\",\"N\\\/A\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"MINI MOTO 2\",\"222V\",\"AARON MEAD\",\"DNS\",\"N\\\/A\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"MINI MOTO 3\",\"29\",\"AUDREY FRAMAND\",\"20\",\"N\\\/A\",\"20\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"15\"],[\"MINI MOTO 3\",\"66\",\"RYAN MORIN\",\"X\",\"N\\\/A\",\"14\",\"N\\\/A\",\"N\\\/A\",\"25\",\"22\"],[\"MINI MOTO 3\",\"59\",\"CLAUDE DENONCOURT\",\"X\",\"N\\\/A\",\"25\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"MINI MOTO 3\",\"313\",\"SARAH MICHELLE COTTON\",\"25\",\"N\\\/A\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"MINI MOTO 3\",\"686\",\"FRED MONTMINY SAMSON\",\"X\",\"N\\\/A\",\"22\",\"N\\\/A\",\"N\\\/A\",\"X\",\"25\"],[\"MINI MOTO 3\",\"11\",\"DEACON HOFFMAN\",\"22\",\"N\\\/A\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"MINI MOTO 3\",\"42\",\"JAYSON MONGEON\",\"X\",\"N\\\/A\",\"18\",\"N\\\/A\",\"N\\\/A\",\"DNF\",\"20\"],[\"MINI MOTO 3\",\"28\",\"NATHALIE VEZEAU\",\"18\",\"N\\\/A\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"MINI MOTO 3\",\"439\",\"JULIE SIMARD\",\"X\",\"N\\\/A\",\"16\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"X\"],[\"MINI MOTO 3\",\"913\",\"ABEL BYBERG\",\"16\",\"N\\\/A\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"MINI MOTO 3\",\"1\",\"OCTAVIA PINEL BOURSIER\",\"X\",\"N\\\/A\",\"15\",\"N\\\/A\",\"N\\\/A\",\"X\",\"16\"],[\"MINI MOTO 3\",\"33\",\"MARTINE BUSSI\\u00c8RE\",\"X\",\"N\\\/A\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"MINI MOTO 4\",\"21\",\"MAXIME BELLEROPHON\",\"X\",\"N\\\/A\",\"25\",\"N\\\/A\",\"N\\\/A\",\"25\",\"25\"],[\"MINI MOTO 4\",\"42\",\"JAYSON MONGEON\",\"X\",\"N\\\/A\",\"22\",\"N\\\/A\",\"N\\\/A\",\"22\",\"22\"],[\"MINI MOTO 4\",\"19\",\"EVERLY LANDRY\",\"25\",\"N\\\/A\",\"X\",\"N\\\/A\",\"N\\\/A\",\"18\",\"X\"],[\"MINI MOTO 4\",\"1\",\"OCTAVIA PINEL BOURSIER\",\"22\",\"N\\\/A\",\"18\",\"N\\\/A\",\"N\\\/A\",\"X\",\"15\"],[\"MINI MOTO 4\",\"66\",\"RYAN MORIN\",\"X\",\"N\\\/A\",\"20\",\"N\\\/A\",\"N\\\/A\",\"20\",\"20\"],[\"MINI MOTO 4\",\"3\",\"CROSBY BYBERG\",\"20\",\"N\\\/A\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"MINI MOTO 4\",\"28\",\"NATALIA TANASIC\",\"18\",\"N\\\/A\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"MINI MOTO 4\",\"905\",\"ABBY DUBUC\",\"X\",\"N\\\/A\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"18\"],[\"MINI MOTO 4\",\"11\",\"ISAAC DUPUIS\",\"X\",\"N\\\/A\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"16\"]],\"SBQ - 2026\":[[\"CATEGORY\",\"#\",\"NAME\",\"MECAGLISSE\",\"MONTMAGNY 1\",\"MONTMAGNY 2\"],[\"SS 1\",\"954\",\"HENRI-PIERRE TREMBLAY\",\"25\",\"22\",\"25\"],[\"SS 1\",\"57\",\"LAURENT LALIBERT\\u00c9 GIRARD\",\"25\",\"25\",\"20\"],[\"SS 1\",\"951\",\"PASCAL TREMBLAY\",\"25\",\"16\",\"22\"],[\"SS 1\",\"213\",\"JONATHAN MASSIGNANI\",\"25\",\"20\",\"18\"],[\"SS 1\",\"145\",\"GABRIEL ST-PIERRE\",\"25\",\"X\",\"16\"],[\"SS 1\",\"88\",\"ALEXANDRE LESSARD\",\"25\",\"X\",\"X\"],[\"SS 1\",\"66\",\"DEVEN ARSENEAU\",\"25\",\"X\",\"X\"],[\"SS 1\",\"72\",\"ERNEST BERNHARD\",\"25\",\"X\",\"X\"],[\"SS 1\",\"805\",\"GUY GRANDBOIS\",\"25\",\"X\",\"X\"],[\"SS 1\",\"95\",\"MARC TREMBLAY\",\"25\",\"X\",\"X\"],[\"SS 1\",\"327\",\"STEVE GARNEAU\",\"25\",\"X\",\"X\"],[\"SS 1\",\"122\",\"ISRAEL GOSSELIN\",\"X\",\"18\",\"X\"],[\"SS 1\",\"9\",\"DIMITRI GODARD\",\"X\",\"X\",\"DNS\"],[\"SS 1\",\"666\",\"JEAN LAMBERT\",\"X\",\"X\",\"DNS\"],[\"SS 1\",\"222\",\"MAVRICK CYR\",\"X\",\"DNS\",\"X\"],[\"SBK 1\",\"57\",\"LAURENT LALIBERT\\u00c9 GIRARD\",\"15\",\"25\",\"18\"],[\"SBK 1\",\"954\",\"HENRI-PIERRE TREMBLAY\",\"20\",\"22\",\"16\"],[\"SBK 1\",\"213\",\"JONATHAN MASSIGNANI\",\"18\",\"13\",\"25\"],[\"SBK 1\",\"951\",\"PASCAL TREMBLAY\",\"16\",\"18\",\"20\"],[\"SBK 1\",\"122\",\"ISRAEL GOSSELIN\",\"X\",\"16\",\"22\"],[\"SBK 1\",\"95\",\"MARC TREMBLAY\",\"13\",\"20\",\"X\"],[\"SBK 1\",\"145\",\"GABRIEL ST-PIERRE\",\"12\",\"X\",\"15\"],[\"SBK 1\",\"72\",\"ERNEST BERNHARD\",\"25\",\"X\",\"X\"],[\"SBK 1\",\"327\",\"STEVE GARNEAU\",\"22\",\"X\",\"X\"],[\"SBK 1\",\"9\",\"DIMITRI GODARD\",\"X\",\"15\",\"X\"],[\"SBK 1\",\"172\",\"ERICK GOSSELIN\",\"X\",\"14\",\"DNS\"],[\"SBK 1\",\"66\",\"DEVEN ARSENEAU\",\"14\",\"X\",\"X\"],[\"SBK 1\",\"88\",\"ALEXANDRE LESSARD\",\"11\",\"X\",\"X\"],[\"SBK 1\",\"666\",\"JEAN LAMBERT\",\"X\",\"DNF\",\"DNS\"],[\"SBK 1\",\"88\",\"MARC LABOSSI\\u00c8RE\",\"X\",\"X\",\"DNS\"],[\"SBK 1\",\"104\",\"MAVRICK CYR\",\"X\",\"X\",\"DNS\"],[\"SBK 1\",\"24\",\"S\\u00c9BASTIEN TREMBLAY\",\"X\",\"X\",\"DNS\"],[\"SBK 1\",\"805\",\"GUY GRANDBOIS\",\"DNS\",\"X\",\"X\"],[\"SS 2\",\"105\",\"KEVEN LAROCHELLE\",\"25\",\"25\",\"25\"],[\"SS 2\",\"588\",\"ST\\u00c9PHANE BELZIL\",\"25\",\"20\",\"18\"],[\"SS 2\",\"222\",\"CHELSEA PARENT-BOURSIER\",\"25\",\"15\",\"15\"],[\"SS 2\",\"69\",\"RAPHAEL C. RODRIGUEZ\",\"25\",\"13\",\"11\"],[\"SS 2\",\"511\",\"VINCENT ROY\",\"X\",\"22\",\"22\"],[\"SS 2\",\"950\",\"HUBERT TESSIER\",\"25\",\"16\",\"X\"],[\"SS 2\",\"191\",\"ANDERSONE RODRIGUES\",\"25\",\"X\",\"13\"],[\"SS 2\",\"112\",\"JEAN-FRANCOIS PERREAULT\",\"25\",\"DNS\",\"12\"],[\"SS 2\",\"922\",\"JEAN-PHILIPPE AUDET HALL\",\"25\",\"12\",\"X\"],[\"SS 2\",\"95\",\"ALEXIS BAHL\",\"25\",\"DNS\",\"X\"],[\"SS 2\",\"99\",\"BENOIT DRAPEAU\",\"25\",\"X\",\"X\"],[\"SS 2\",\"35\",\"CL\\u00c9MENT LAROCHE\",\"25\",\"X\",\"X\"],[\"SS 2\",\"292\",\"FR\\u00c9D\\u00c9RIC GAUTHIER DOYLE\",\"25\",\"X\",\"X\"],[\"SS 2\",\"70\",\"GUILLAUME REEVES VACHON\",\"25\",\"X\",\"X\"],[\"SS 2\",\"88\",\"JOANNIE CHR\\u00c9TIEN\",\"25\",\"X\",\"X\"],[\"SS 2\",\"533\",\"MARTIN VILLENEUVE\",\"25\",\"X\",\"X\"],[\"SS 2\",\"710\",\"NATHAN ROY GIROUX\",\"25\",\"X\",\"X\"],[\"SS 2\",\"281\",\"VALERIAN RENAUD\",\"25\",\"X\",\"X\"],[\"SS 2\",\"469\",\"VINCENT ROBITAILLE\",\"25\",\"X\",\"X\"],[\"SS 2\",\"24\",\"YANNICK LAFLAMME\",\"25\",\"X\",\"X\"],[\"SS 2\",\"68\",\"MATHIEU DUPERRON\",\"X\",\"X\",\"20\"],[\"SS 2\",\"366\",\"XAVIER MARTEL\",\"X\",\"18\",\"X\"],[\"SS 2\",\"16\",\"MIGUEL GAUDREAULT\",\"X\",\"X\",\"16\"],[\"SS 2\",\"691\",\"ALEXANDRE GENDREAU\",\"X\",\"X\",\"14\"],[\"SS 2\",\"88\",\"PIER ANTOINE DALLAIRE\",\"X\",\"14\",\"X\"],[\"SS 2\",\"742\",\"ANTHONY DAPICE\",\"X\",\"X\",\"10\"],[\"SBK 2\",\"105\",\"KEVEN LAROCHELLE\",\"25\",\"25\",\"25\"],[\"SBK 2\",\"710\",\"NATHAN ROY GIROUX\",\"12\",\"20\",\"22\"],[\"SBK 2\",\"222\",\"CHELSEA PARENT-BOURSIER\",\"20\",\"16\",\"18\"],[\"SBK 2\",\"588\",\"ST\\u00c9PHANE BELZIL\",\"18\",\"18\",\"16\"],[\"SBK 2\",\"11\",\"SEBASTIEN BOUCHARD\",\"X\",\"22\",\"20\"],[\"SBK 2\",\"281\",\"VALERIAN RENAUD\",\"16\",\"X\",\"14\"],[\"SBK 2\",\"191\",\"ANDERSONE RODRIGUES\",\"14\",\"X\",\"15\"],[\"SBK 2\",\"112\",\"JEAN-FRANCOIS PERREAULT\",\"13\",\"DNS\",\"12\"],[\"SBK 2\",\"99\",\"BENOIT DRAPEAU\",\"22\",\"X\",\"DNS\"],[\"SBK 2\",\"533\",\"MARTIN VILLENEUVE\",\"7\",\"14\",\"X\"],[\"SBK 2\",\"366\",\"XAVIER MARTEL\",\"X\",\"15\",\"X\"],[\"SBK 2\",\"70\",\"GUILLAUME REEVES VACHON\",\"15\",\"X\",\"X\"],[\"SBK 2\",\"42\",\"DOMINIC ANDR\\u00c9-MONGEON\",\"X\",\"X\",\"13\"],[\"SBK 2\",\"66\",\"AUDREY DURAND CHARLAND\",\"X\",\"13\",\"X\"],[\"SBK 2\",\"88\",\"PIER ANTOINE DALLAIRE\",\"X\",\"12\",\"X\"],[\"SBK 2\",\"752\",\"ANTHONY DAPICE\",\"X\",\"X\",\"11\"],[\"SBK 2\",\"95\",\"ALEXIS BAHL\",\"11\",\"X\",\"X\"],[\"SBK 2\",\"922\",\"JEAN-PHILIPPE AUDET HALL\",\"10\",\"X\",\"X\"],[\"SBK 2\",\"69\",\"RAPHAEL C. RODRIGUEZ\",\"9\",\"X\",\"X\"],[\"SBK 2\",\"292\",\"FR\\u00c9D\\u00c9RIC GAUTHIER DOYLE\",\"8\",\"X\",\"X\"],[\"SBK 2\",\"35\",\"CL\\u00c9MENT LAROCHE\",\"DNS\",\"X\",\"X\"],[\"SBK 2\",\"950\",\"HUBERT TESSIER\",\"DNS\",\"X\",\"X\"],[\"SBK 2\",\"88\",\"JOANNIE CHR\\u00c9TIEN\",\"DNS\",\"X\",\"X\"],[\"SBK 2\",\"469\",\"VINCENT ROBITAILLE\",\"DNS\",\"X\",\"X\"],[\"SBK 2\",\"24\",\"YANNICK LAFLAMME\",\"DNS\",\"X\",\"X\"],[\"SBK 3\",\"439\",\"JULIE SIMARD\",\"25\",\"22\",\"18\"],[\"SBK 3\",\"27\",\"GABRIELLE FUG\\u00c8RE\",\"22\",\"18\",\"20\"],[\"SBK 3\",\"222\",\"CHELSEA PARENT-BOURSIER\",\"DNS\",\"25\",\"25\"],[\"SBK 3\",\"213\",\"JULIE LALONDE\",\"20\",\"14\",\"15\"],[\"SBK 3\",\"313\",\"SARAH MICHELLE COTTON\",\"X\",\"16\",\"14\"],[\"SBK 3\",\"3\",\"JADE ROYER\",\"X\",\"12\",\"13\"],[\"SBK 3\",\"33\",\"MARTINE BUSSI\\u00c8RE\",\"X\",\"13\",\"11\"],[\"SBK 3\",\"381\",\"EVELYNE TALBOT\",\"X\",\"X\",\"22\"],[\"SBK 3\",\"68\",\"CATHERINE LALIBERT\\u00c9\",\"X\",\"10\",\"12\"],[\"SBK 3\",\"708\",\"JULIE PROULX\",\"X\",\"20\",\"X\"],[\"SBK 3\",\"292\",\"AUDREY FRAMAND\",\"18\",\"X\",\"DNS\"],[\"SBK 3\",\"127\",\"JESSICA DIARRA\",\"X\",\"X\",\"16\"],[\"SBK 3\",\"66\",\"AUDREY DURAND CHARLAND\",\"X\",\"15\",\"X\"],[\"SBK 3\",\"22\",\"MC\",\"X\",\"11\",\"DNS\"],[\"SBK 3\",\"67\",\"ALLISON LIVERNOCHE\",\"X\",\"X\",\"DNS\"],[\"SBK 3\",\"88\",\"JOANNIE CHR\\u00c9TIEN\",\"DNF\",\"X\",\"X\"],[\"SBK 4\",\"95\",\"MARC TREMBLAY\",\"22\",\"25\",\"25\"],[\"SBK 4\",\"213\",\"JONATHAN MASSIGNANI\",\"25\",\"22\",\"22\"],[\"SBK 4\",\"588\",\"ST\\u00c9PHANE BELZIL\",\"20\",\"18\",\"18\"],[\"SBK 4\",\"69\",\"RAPHAEL C. RODRIGUEZ\",\"16\",\"12\",\"14\"],[\"SBK 4\",\"9\",\"DIMITRI GODARD\",\"X\",\"20\",\"20\"],[\"SBK 4\",\"122\",\"KEN ARMSTRONG\",\"X\",\"16\",\"16\"],[\"SBK 4\",\"469\",\"VINCENT ROBITAILLE\",\"15\",\"14\",\"X\"],[\"SBK 4\",\"341\",\"DAVID BERTRAND\",\"X\",\"13\",\"13\"],[\"SBK 4\",\"88\",\"ALEXANDRE LESSARD\",\"18\",\"X\",\"X\"],[\"SBK 4\",\"281\",\"VALERIAN RENAUD\",\"X\",\"X\",\"15\"],[\"SBK 4\",\"172\",\"ERICK GOSSELIN\",\"X\",\"15\",\"X\"],[\"SBK 4\",\"99\",\"BENOIT DRAPEAU\",\"DNS\",\"X\",\"DNS\"],[\"SBK 4\",\"104\",\"JEAN-FRAN\\u00c7OIS CYR\",\"X\",\"X\",\"DNS\"],[\"SBK 4\",\"805\",\"GUY GRANDBOIS\",\"DNS\",\"X\",\"X\"],[\"SBK 4\",\"717\",\"TOMMY PAQUETTE\",\"DNS\",\"X\",\"X\"],[\"LW\",\"222\",\"CHELSEA PARENT-BOURSIER\",\"22\",\"25\",\"25\"],[\"LW\",\"24\",\"YANNICK LAFLAMME\",\"18\",\"15\",\"16\"],[\"LW\",\"27\",\"GABRIELLE FUG\\u00c8RE\",\"13\",\"14\",\"15\"],[\"LW\",\"145\",\"GABRIEL ST-PIERRE\",\"15\",\"X\",\"20\"],[\"LW\",\"49\",\"BENOIT B\\u00c9LAIR\",\"X\",\"18\",\"14\"],[\"LW\",\"922\",\"JEAN-PHILIPPE AUDET HALL\",\"16\",\"16\",\"X\"],[\"LW\",\"22\",\"ZAIM LAFLAMME\",\"25\",\"X\",\"X\"],[\"LW\",\"22\",\"MAVRICK CYR\",\"X\",\"X\",\"22\"],[\"LW\",\"52\",\"THIBAULT GILBERT\",\"DNS\",\"22\",\"X\"],[\"LW\",\"9\",\"DIMITRI GODARD\",\"X\",\"20\",\"DNS\"],[\"LW\",\"95\",\"ALEXIS BAHL\",\"20\",\"DNS\",\"X\"],[\"LW\",\"21\",\"MAXIME BELLEROPHON\",\"DNS\",\"X\",\"X\"],[\"LW\",\"760\",\"WILLIAM GENEST\",\"X\",\"X\",\"18\"],[\"LW\",\"292\",\"FR\\u00c9D\\u00c9RIC GAUTHIER DOYLE\",\"14\",\"X\",\"X\"],[\"LW\",\"29\",\"AUDREY FRAMAND\",\"12\",\"X\",\"DNS\"],[\"LW\",\"439\",\"JULIE SIMARD\",\"DNS\",\"X\",\"DNS\"],[\"LW\",\"717\",\"TOMMY PAQUETTE\",\"DNS\",\"X\",\"X\"]],\"SMO - 2026\":[[\"CATEGORY\",\"#\",\"NAME\",\"SHANNONVILLE\",\"LOMBARDY\"],[\"SM 1\",\"21\",\"XAVIER BELLEROPHON\",\"22\",\"25\"],[\"SM 1\",\"24\",\"S\\u00c9BASTIEN TREMBLAY\",\"20\",\"22\"],[\"SM 1\",\"654\",\"JACOB BILODEAU\",\"12\",\"18\"],[\"SM 1\",\"93\",\"ALEX DESHAIES\",\"14\",\"13\"],[\"SM 1\",\"1\",\"NELSON JR MOREAU\",\"25\",\"X\"],[\"SM 1\",\"9\",\"DIMITRI GODARD\",\"11\",\"14\"],[\"SM 1\",\"104\",\"MAVRICK CYR\",\"X\",\"20\"],[\"SM 1\",\"22\",\"ZAIM LAFLAMME\",\"18\",\"X\"],[\"SM 1\",\"67\",\"JIMMY MORIN\",\"X\",\"16\"],[\"SM 1\",\"43\",\"PAUL-\\u00c9TIENNE COURTOIS\",\"16\",\"X\"],[\"SM 1\",\"44\",\"GABRIEL VALL\\u00c9E\",\"X\",\"15\"],[\"SM 1\",\"88\",\"THOMAS CASAS\",\"15\",\"X\"],[\"SM 1\",\"94\",\"LUDOVICK S\\u00c9VIGNY\",\"13\",\"X\"],[\"SM 1\",\"79\",\"MACKENZIE WEIL\",\"10\",\"X\"],[\"SM 1\",\"87\",\"DAVID GUERTIN\",\"9\",\"X\"],[\"SM 1\",\"3\",\"MARTIN CARPENTIER\",\"8\",\"X\"],[\"SM 1\",\"34\",\"DAVE AUGER\",\"DNS\",\"X\"],[\"SM 1\",\"X\",\"VINCENT WILSON\",\"DNS\",\"X\"],[\"SM 2\",\"7\",\"THOMAS MANSEAU\",\"18\",\"25\"],[\"SM 2\",\"229\",\"ANTHONY GOSSELIN\",\"20\",\"22\"],[\"SM 2\",\"27\",\"JEREMIE LAUZI\\u00c8RE\",\"22\",\"20\"],[\"SM 2\",\"158\",\"THOMAS LAROUCHE\",\"25\",\"16\"],[\"SM 2\",\"213\",\"JONATHAN MASSIGNANI\",\"15\",\"15\"],[\"SM 2\",\"177\",\"XAVIER MALTAIS\",\"10\",\"13\"],[\"SM 2\",\"68\",\"JAGER STOCKILL\",\"DNS\",\"18\"],[\"SM 2\",\"717\",\"TOMMY PAQUETTE\",\"16\",\"X\"],[\"SM 2\",\"13\",\"ROMAIN LANGLET\",\"X\",\"14\"],[\"SM 2\",\"38\",\"CHRIS BARTCZAK\",\"14\",\"X\"],[\"SM 2\",\"74\",\"CEDRICK BOURGEOIS TURCOTTE\",\"13\",\"X\"],[\"SM 2\",\"16\",\"SYLVAIN FOLLENFANT\",\"X\",\"12\"],[\"SM 2\",\"78\",\"CLAUDE SERGERIE\",\"12\",\"DNS\"],[\"SM 2\",\"77\",\"ADRIEN JACQUART\",\"11\",\"X\"],[\"SM 2\",\"75\",\"FRANCK NUNES\",\"DNS\",\"X\"],[\"SM 3\",\"10\",\"MARC DUMONT\",\"20\",\"22\"],[\"SM 3\",\"1\",\"CHELSEA PARENT-BOURSIER\",\"18\",\"15\"],[\"SM 3\",\"281\",\"VALERIAN RENAUD\",\"13\",\"18\"],[\"SM 3\",\"42\",\"DOMINIC ANDR\\u00c9-MONGEON\",\"X\",\"25\"],[\"SM 3\",\"321\",\"DANY MARCHAND\",\"25\",\"X\"],[\"SM 3\",\"51\",\"MARTIN PARENT\",\"16\",\"9\"],[\"SM 3\",\"404\",\"SHAWN ALEXIS GRONDIN GAUTHIER\",\"10\",\"14\"],[\"SM 3\",\"16\",\"SYLVAIN FOLLENFANT\",\"22\",\"X\"],[\"SM 3\",\"97\",\"DYLAN SCOTT PAQUETTE\",\"12\",\"10\"],[\"SM 3\",\"17\",\"SALOMON H\\u00c9RARD\",\"X\",\"20\"],[\"SM 3\",\"450\",\"ALEXIS MARINEAU\",\"DNS\",\"16\"],[\"SM 3\",\"40\",\"BEN BELLEVILLE\",\"15\",\"X\"],[\"SM 3\",\"11\",\"TOJO\",\"14\",\"X\"],[\"SM 3\",\"248\",\"JULIEN GALARNEAU\",\"X\",\"13\"],[\"SM 3\",\"99\",\"BENOIT DRAPEAU\",\"X\",\"12\"],[\"SM 3\",\"78\",\"JULIEN DUBOIS\",\"X\",\"11\"],[\"SM 3\",\"393\",\"RAPHA\\u00cbL LEMIRE\",\"11\",\"X\"],[\"SM 3\",\"70\",\"GUILLAUME REEVES VACHON\",\"9\",\"X\"],[\"SM 3\",\"145\",\"GABRIEL ST-PIERRE\",\"8\",\"X\"],[\"SM 3\",\"85\",\"BEN GRAHAM\",\"7\",\"X\"],[\"SM 3\",\"330\",\"ANTOINE GAGNON\",\"6\",\"X\"],[\"SM 3\",\"29\",\"MARIO DUMAS\",\"DNS\",\"DNS\"],[\"SM 3\",\"533\",\"MARTIN VILLENEUVE\",\"DNS\",\"DNS\"],[\"SM 3\",\"26\",\"ARNAUD BOURCIER\",\"DNS\",\"X\"],[\"SM 3\",\"323\",\"PIERRE JUNIOR CHARRETTE\",\"DNS\",\"X\"],[\"SM 4\",\"21\",\"XAVIER BELLEROPHON\",\"25\",\"25\"],[\"SM 4\",\"78\",\"CLAUDE SERGERIE\",\"20\",\"16\"],[\"SM 4\",\"38\",\"CHRIS BARTCZAK\",\"13\",\"18\"],[\"SM 4\",\"9\",\"DIMITRI GODARD\",\"DNS\",\"22\"],[\"SM 4\",\"321\",\"DANY MARCHAND\",\"22\",\"X\"],[\"SM 4\",\"213\",\"JONATHAN MASSIGNANI\",\"X\",\"20\"],[\"SM 4\",\"51\",\"MARTIN PARENT\",\"18\",\"X\"],[\"SM 4\",\"75\",\"FRANCK NUNES\",\"16\",\"X\"],[\"SM 4\",\"281\",\"VALERIAN RENAUD\",\"X\",\"15\"],[\"SM 4\",\"29\",\"MARIO DUMAS\",\"15\",\"DNS\"],[\"SM 4\",\"292\",\"AUDREY FRAMAND\",\"14\",\"DNS\"],[\"SM 4\",\"533\",\"MARTIN VILLENEUVE\",\"X\",\"DNS\"],[\"SM 4\",\"323\",\"PIERRE JUNIOR CHARRETTE\",\"DNS\",\"X\"],[\"SM 4\",\"24\",\"YANNICK LAFLAMME\",\"DNS\",\"X\"],[\"SM 5\",\"33\",\"S\\u00c9BASTIEN LABELLE\",\"25\",\"25\"],[\"SM 5\",\"367\",\"MARIO BERGERON\",\"22\",\"20\"],[\"SM 5\",\"35\",\"CL\\u00c9MENT LAROCHE\",\"DNF\",\"22\"],[\"SM 5\",\"26\",\"ARNAUD BOURCIER\",\"DNS\",\"X\"],[\"SM 5\",\"29\",\"MARIO DUMAS\",\"DNS\",\"X\"],[\"SM 6\",\"1\",\"CHELSEA PARENT-BOURSIER\",\"25\",\"25\"],[\"SM 6\",\"22\",\"JULIE LALONDE\",\"15\",\"20\"],[\"SM 6\",\"654\",\"JULIE SIMARD\",\"X\",\"22\"],[\"SM 6\",\"43\",\"M\\u00c9LODIE H\\u00c9BERT BOURASSA\",\"22\",\"X\"],[\"SM 6\",\"313\",\"SARAH MICHELLE COTTON\",\"20\",\"X\"],[\"SM 6\",\"77\",\"LISA CHABOT\",\"18\",\"X\"],[\"SM 6\",\"3\",\"ISABELLE G\\u00c9LINAS\",\"16\",\"X\"],[\"SM 6\",\"28\",\"NATHALIE VEZEAU\",\"14\",\"X\"],[\"SM 6\",\"292\",\"AUDREY FRAMAND\",\"13\",\"DNS\"],[\"SM 6\",\"222\",\"MARIE-CLAUDE PARENT\",\"X\",\"DNS\"],[\"SM 6\",\"42\",\"MAUDE DESCH\\u00c8NES\",\"DNS\",\"DNS\"],[\"SM 6\",\"20\",\"KIM ISABELLE\",\"DNS\",\"X\"],[\"SM 7\",\"2\",\"CHELSEA PARENT-BOURSIER\",\"DNS\",\"25\"],[\"SM 7\",\"17\",\"SALOMON H\\u00c9RARD\",\"X\",\"22\"],[\"SM 7\",\"78\",\"JULIEN DUBOIS\",\"X\",\"20\"],[\"SM 7\",\"21\",\"MAXIME BELLEROPHON\",\"X\",\"18\"],[\"SM 7\",\"42\",\"JAYSON MONGEON\",\"X\",\"16\"],[\"SM 7\",\"68\",\"JAGER STOCKILL\",\"DNS\",\"X\"],[\"SM 7\",\"124\",\"THOMAS LAROUCHE\",\"DNS\",\"X\"],[\"SM 8\",\"21\",\"MAXIME BELLEROPHON\",\"25\",\"25\"],[\"SM 8\",\"244\",\"CHARLES MALTAIS\",\"14\",\"16\"],[\"SM 8\",\"42\",\"JAYSON MONGEON\",\"X\",\"22\"],[\"SM 8\",\"1\",\"OCTAVIA PINEL BOURSIER\",\"22\",\"X\"],[\"SM 8\",\"66\",\"RYAN MORIN\",\"X\",\"20\"],[\"SM 8\",\"913\",\"ABEL BYBERG\",\"20\",\"X\"],[\"SM 8\",\"5\",\"JAYDEN VALL\\u00c9E\",\"X\",\"18\"],[\"SM 8\",\"3\",\"CROSBY BYBERG\",\"18\",\"X\"],[\"SM 8\",\"89\",\"COLE HODGSON\",\"16\",\"X\"],[\"SM 8\",\"117\",\"LOIC MALTAIS\",\"15\",\"DNS\"],[\"SM 8\",\"19\",\"EVERLY LANDRY\",\"X\",\"DNS\"],[\"SM 9\",\"1\",\"CHELSEA PARENT-BOURSIER\",\"25\",\"25\"],[\"SM 9\",\"439\",\"JULIE SIMARD\",\"20\",\"22\"],[\"SM 9\",\"22\",\"JULIE LALONDE\",\"15\",\"18\"],[\"SM 9\",\"43\",\"M\\u00c9LODIE H\\u00c9BERT BOURASSA\",\"22\",\"X\"],[\"SM 9\",\"27\",\"GABRIELLE FUG\\u00c8RE\",\"X\",\"20\"],[\"SM 9\",\"313\",\"SARAH MICHELLE COTTON\",\"18\",\"X\"],[\"SM 9\",\"533\",\"MARTINE BUSSI\\u00c8RE\",\"X\",\"16\"],[\"SM 9\",\"3\",\"ISABELLE G\\u00c9LINAS\",\"16\",\"X\"],[\"SM 9\",\"1x\",\"MARIE-CLAUDE PARENT\",\"X\",\"15\"],[\"SM 9\",\"28\",\"NATHALIE VEZEAU\",\"14\",\"X\"],[\"SM 9\",\"292\",\"AUDREY FRAMAND\",\"13\",\"DNS\"],[\"SM 9\",\"67\",\"ALLISON LIVERNOCHE\",\"X\",\"DNS\"],[\"SM 9\",\"42\",\"MAUDE DESCH\\u00c8NES\",\"DNS\",\"DNS\"],[\"SM 9\",\"99\",\"EMILY DICKSON\",\"DNS\",\"X\"],[\"SM 9\",\"20\",\"KIM ISABELLE\",\"DNS\",\"X\"],[\"SM 9\",\"77\",\"LISA CHABOT\",\"DNS\",\"X\"],[\"SM 10\",\"10\",\"MARC DUMONT\",\"16\",\"22\"],[\"SM 10\",\"450\",\"ALEXIS MARINEAU\",\"12\",\"25\"],[\"SM 10\",\"1\",\"CHELSEA PARENT-BOURSIER\",\"11\",\"18\"],[\"SM 10\",\"404\",\"SHAWN ALEXIS GRONDIN GAUTHIER\",\"6\",\"20\"],[\"SM 10\",\"321\",\"DANY MARCHAND\",\"25\",\"X\"],[\"SM 10\",\"40\",\"BEN BELLEVILLE\",\"22\",\"X\"],[\"SM 10\",\"281\",\"VALERIAN RENAUD\",\"7\",\"14\"],[\"SM 10\",\"11\",\"TOJO\",\"20\",\"X\"],[\"SM 10\",\"16\",\"SYLVAIN FOLLENFANT\",\"18\",\"X\"],[\"SM 10\",\"99\",\"BENOIT DRAPEAU\",\"X\",\"16\"],[\"SM 10\",\"17\",\"SALOMON H\\u00c9RARD\",\"X\",\"15\"],[\"SM 10\",\"51\",\"MARTIN PARENT\",\"15\",\"X\"],[\"SM 10\",\"33\",\"S\\u00c9BASTIEN LABELLE\",\"5\",\"10\"],[\"SM 10\",\"482\",\"KEWINE SEDRICK\",\"14\",\"X\"],[\"SM 10\",\"35\",\"CL\\u00c9MENT LAROCHE\",\"3\",\"11\"],[\"SM 10\",\"104\",\"DOMINIC ANDR\\u00c9-MONGEON\",\"X\",\"13\"],[\"SM 10\",\"97\",\"DYLAN SCOTT PAQUETTE\",\"13\",\"DNS\"],[\"SM 10\",\"78\",\"JULIEN DUBOIS\",\"X\",\"12\"],[\"SM 10\",\"330\",\"ANTOINE GAGNON\",\"10\",\"X\"],[\"SM 10\",\"70\",\"GUILLAUME REEVES VACHON\",\"9\",\"X\"],[\"SM 10\",\"15\",\"ESTEBAN CANO\",\"8\",\"X\"],[\"SM 10\",\"717\",\"TOMMY PAQUETTE\",\"4\",\"X\"],[\"SM 10\",\"5\",\"MAX BROYDA\",\"2\",\"X\"],[\"SM 10\",\"145\",\"GABRIEL ST-PIERRE\",\"1\",\"X\"],[\"SM 10\",\"292\",\"FR\\u00c9D\\u00c9RIC GAUTHIER DOYLE\",\"0\",\"X\"],[\"SM 10\",\"313\",\"SARAH MICHELLE COTTON\",\"0\",\"X\"],[\"SM 10\",\"922\",\"JEAN-PHILIPPE AUDET HALL\",\"DNS\",\"DNS\"],[\"SM 10\",\"29\",\"MARIO DUMAS\",\"DNS\",\"DNS\"],[\"SM 10\",\"533\",\"MARTIN VILLENEUVE\",\"DNS\",\"DNS\"],[\"SM 10\",\"26\",\"ARNAUD BOURCIER\",\"DNS\",\"X\"],[\"SM 10\",\"85\",\"BEN GRAHAM\",\"DNS\",\"X\"],[\"SM 10\",\"84\",\"BENJAMIN HODGSON\",\"DNS\",\"X\"],[\"SM 10\",\"140\",\"FRANCIS VALOIS\",\"DNS\",\"X\"],[\"SM 10\",\"325\",\"PIERRE JUNIOR CHARRETTE\",\"DNS\",\"X\"],[\"SM 10\",\"24\",\"YANNICK LAFLAMME\",\"DNS\",\"X\"],[\"SM 11\",\"7\",\"THOMAS MANSEAU\",\"20\",\"22\"],[\"SM 11\",\"27\",\"JEREMIE LAUZI\\u00c8RE\",\"25\",\"16\"],[\"SM 11\",\"229\",\"ANTHONY GOSSELIN\",\"16\",\"18\"],[\"SM 11\",\"213\",\"JONATHAN MASSIGNANI\",\"14\",\"15\"],[\"SM 11\",\"13\",\"ROMAIN LANGLET\",\"X\",\"25\"],[\"SM 11\",\"38\",\"CHRIS BARTCZAK\",\"11\",\"12\"],[\"SM 11\",\"75\",\"FRANCK NUNES\",\"22\",\"X\"],[\"SM 11\",\"177\",\"XAVIER MALTAIS\",\"8\",\"13\"],[\"SM 11\",\"16\",\"SYLVAIN FOLLENFANT\",\"X\",\"20\"],[\"SM 11\",\"717\",\"TOMMY PAQUETTE\",\"18\",\"X\"],[\"SM 11\",\"74\",\"CEDRICK BOURGEOIS TURCOTTE\",\"15\",\"X\"],[\"SM 11\",\"68\",\"JAGER STOCKILL\",\"DNS\",\"14\"],[\"SM 11\",\"158\",\"THOMAS LAROUCHE\",\"13\",\"X\"],[\"SM 11\",\"78\",\"CLAUDE SERGERIE\",\"12\",\"DNS\"],[\"SM 11\",\"77\",\"ADRIEN JACQUART\",\"10\",\"X\"],[\"SM 11\",\"88R\",\"LAURENT LALIBERT\\u00c9 GIRARD\",\"9\",\"X\"],[\"SM 12\",\"24\",\"S\\u00c9BASTIEN TREMBLAY\",\"22\",\"25\"],[\"SM 12\",\"93\",\"ALEX DESHAIES\",\"15\",\"22\"],[\"SM 12\",\"9\",\"DIMITRI GODARD\",\"16\",\"18\"],[\"SM 12\",\"21\",\"XAVIER BELLEROPHON\",\"14\",\"16\"],[\"SM 12\",\"1\",\"NELSON JR MOREAU\",\"25\",\"X\"],[\"SM 12\",\"104\",\"MAVRICK CYR\",\"X\",\"20\"],[\"SM 12\",\"43\",\"PAUL-\\u00c9TIENNE COURTOIS\",\"20\",\"X\"],[\"SM 12\",\"22\",\"ZAIM LAFLAMME\",\"18\",\"X\"],[\"SM 12\",\"67\",\"JIMMY MORIN\",\"X\",\"15\"],[\"SM 12\",\"807\",\"GABRIEL VALL\\u00c9E\",\"X\",\"14\"],[\"SM 12\",\"654\",\"JACOB BILODEAU\",\"13\",\"X\"],[\"SM 12\",\"94\",\"LUDOVICK S\\u00c9VIGNY\",\"12\",\"X\"],[\"SM 12\",\"87\",\"DAVID GUERTIN\",\"11\",\"X\"],[\"SM 12\",\"88\",\"VINCENT WILSON\",\"10\",\"X\"],[\"SM 12\",\"3\",\"MARTIN CARPENTIER\",\"9\",\"X\"],[\"SM 12\",\"34\",\"DAVE AUGER\",\"DNS\",\"X\"],[\"SM 12\",\"79\",\"MACKENZIE WEIL\",\"DNS\",\"X\"],[\"LW\",\"222\",\"CHELSEA PARENT-BOURSIER\",\"20\",\"22\"],[\"LW\",\"24\",\"YANNICK LAFLAMME\",\"13\",\"18\"],[\"LW\",\"922\",\"JEAN-PHILIPPE AUDET HALL\",\"14\",\"15\"],[\"LW\",\"248\",\"JULIEN GALARNEAU\",\"X\",\"25\"],[\"LW\",\"313\",\"DIMITRI GODARD\",\"25\",\"X\"],[\"LW\",\"717\",\"TOMMY PAQUETTE\",\"22\",\"X\"],[\"LW\",\"22\",\"MAVRICK CYR\",\"X\",\"20\"],[\"LW\",\"22\",\"ZAIM LAFLAMME\",\"18\",\"X\"],[\"LW\",\"760\",\"WILLIAM GENEST\",\"X\",\"16\"],[\"LW\",\"145\",\"GABRIEL ST-PIERRE\",\"16\",\"X\"],[\"LW\",\"49\",\"BENOIT B\\u00c9LAIR\",\"15\",\"X\"],[\"LW\",\"439\",\"JULIE SIMARD\",\"12\",\"DNS\"],[\"LW\",\"21\",\"MAXIME BELLEROPHON\",\"11\",\"X\"],[\"LW\",\"99\",\"EMILY DICKSON\",\"10\",\"X\"],[\"LW\",\"292\",\"FR\\u00c9D\\u00c9RIC GAUTHIER DOYLE\",\"9\",\"X\"],[\"LW\",\"29\",\"AUDREY FRAMAND\",\"8\",\"X\"],[\"LW\",\"27\",\"GABRIELLE FUG\\u00c8RE\",\"X\",\"DNF\"],[\"LW\",\"222V\",\"AARON MEAD\",\"DNF\",\"X\"],[\"MINI MOTO 1\",\"213\",\"JONATHAN MASSIGNANI\",\"22\",\"22\"],[\"MINI MOTO 1\",\"68\",\"JAGER STOCKILL\",\"DNS\",\"25\"],[\"MINI MOTO 1\",\"16\",\"SYLVAIN FOLLENFANT\",\"25\",\"X\"],[\"MINI MOTO 1\",\"248\",\"JULIEN GALARNEAU\",\"X\",\"20\"],[\"MINI MOTO 1\",\"84\",\"BENJAMIN HODGSON\",\"20\",\"X\"],[\"MINI MOTO 1\",\"51\",\"MARTIN PARENT\",\"18\",\"X\"],[\"MINI MOTO 2\",\"222\",\"CHELSEA PARENT-BOURSIER\",\"25\",\"25\"],[\"MINI MOTO 2\",\"21\",\"MAXIME BELLEROPHON\",\"22\",\"20\"],[\"MINI MOTO 2\",\"22\",\"JULIE LALONDE\",\"16\",\"22\"],[\"MINI MOTO 2\",\"85\",\"BEN GRAHAM\",\"20\",\"X\"],[\"MINI MOTO 2\",\"27\",\"COHEN BYBERG\",\"18\",\"X\"],[\"MINI MOTO 2\",\"222V\",\"AARON MEAD\",\"DNS\",\"X\"],[\"MINI MOTO 3\",\"66\",\"RYAN MORIN\",\"X\",\"25\"],[\"MINI MOTO 3\",\"313\",\"SARAH MICHELLE COTTON\",\"25\",\"X\"],[\"MINI MOTO 3\",\"11\",\"DEACON HOFFMAN\",\"22\",\"X\"],[\"MINI MOTO 3\",\"29\",\"AUDREY FRAMAND\",\"20\",\"DNS\"],[\"MINI MOTO 3\",\"28\",\"NATHALIE VEZEAU\",\"18\",\"X\"],[\"MINI MOTO 3\",\"913\",\"ABEL BYBERG\",\"16\",\"X\"],[\"MINI MOTO 3\",\"42\",\"JAYSON MONGEON\",\"X\",\"DNF\"],[\"MINI MOTO 3\",\"439\",\"JULIE SIMARD\",\"X\",\"DNS\"],[\"MINI MOTO 4\",\"19\",\"EVERLY LANDRY\",\"25\",\"18\"],[\"MINI MOTO 4\",\"21\",\"MAXIME BELLEROPHON\",\"X\",\"25\"],[\"MINI MOTO 4\",\"42\",\"JAYSON MONGEON\",\"X\",\"22\"],[\"MINI MOTO 4\",\"1\",\"OCTAVIA PINEL BOURSIER\",\"22\",\"X\"],[\"MINI MOTO 4\",\"66\",\"RYAN MORIN\",\"X\",\"20\"],[\"MINI MOTO 4\",\"3\",\"CROSBY BYBERG\",\"20\",\"X\"],[\"MINI MOTO 4\",\"28\",\"NATALIA TANASIC\",\"18\",\"X\"]],\"SMQ - 2026\":[[\"CATEGORY\",\"#\",\"NAME\",\"SHANNONVILLE\",\"MECAGLISSE\",\"SKRDL\",\"SHAWINIGAN\",\"GP3R-S\",\"GP3R-D\",\"MONTMAGNY 2\",\"KTR\"],[\"SM 1\",\"1\",\"NELSON JR MOREAU\",\"25\",\"25\",\"12\",\"25\",\"22\",\"25\",\"N\\\/A\",\"X\"],[\"SM 1\",\"21\",\"XAVIER BELLEROPHON\",\"22\",\"22\",\"25\",\"DNS\",\"25\",\"18\",\"N\\\/A\",\"25\"],[\"SM 1\",\"654\",\"JACOB BILODEAU\",\"12\",\"18\",\"20\",\"22\",\"20\",\"15\",\"N\\\/A\",\"20\"],[\"SM 1\",\"93\",\"ALEX DESHAIES\",\"14\",\"20\",\"22\",\"15\",\"13\",\"11\",\"N\\\/A\",\"18\"],[\"SM 1\",\"24\",\"S\\u00c9BASTIEN TREMBLAY\",\"20\",\"DNF\",\"DNS\",\"14\",\"8\",\"16\",\"N\\\/A\",\"16\"],[\"SM 1\",\"104\",\"MAVRICK CYR\",\"X\",\"X\",\"14\",\"20\",\"16\",\"20\",\"N\\\/A\",\"22\"],[\"SM 1\",\"777\",\"ZAIM LAFLAMME\",\"18\",\"12\",\"16\",\"DNF\",\"X\",\"X\",\"N\\\/A\",\"X\"],[\"SM 1\",\"87\",\"DAVID GUERTIN\",\"9\",\"DNS\",\"18\",\"DNS\",\"11\",\"8\",\"N\\\/A\",\"DNS\"],[\"SM 1\",\"67\",\"JIMMY MORIN\",\"X\",\"14\",\"11\",\"DNS\",\"12\",\"9\",\"N\\\/A\",\"15\"],[\"SM 1\",\"44\",\"GABRIEL VALL\\u00c9E\",\"X\",\"X\",\"X\",\"16\",\"18\",\"22\",\"N\\\/A\",\"X\"],[\"SM 1\",\"31\",\"PAUL-\\u00c9TIENNE COURTOIS\",\"16\",\"15\",\"X\",\"X\",\"7\",\"7\",\"N\\\/A\",\"X\"],[\"SM 1\",\"9\",\"DIMITRI GODARD\",\"11\",\"X\",\"DNS\",\"DNS\",\"10\",\"12\",\"N\\\/A\",\"DNS\"],[\"SM 1\",\"183\",\"DAVID POTHIER\",\"X\",\"X\",\"X\",\"18\",\"DNF\",\"14\",\"N\\\/A\",\"X\"],[\"SM 1\",\"94\",\"LUDOVICK S\\u00c9VIGNY\",\"13\",\"X\",\"15\",\"13\",\"X\",\"X\",\"N\\\/A\",\"X\"],[\"SM 1\",\"341\",\"DAVID BERTRAND\",\"X\",\"X\",\"X\",\"DNS\",\"15\",\"10\",\"N\\\/A\",\"X\"],[\"SM 1\",\"3\",\"MARTIN CARPENTIER\",\"8\",\"16\",\"X\",\"DNS\",\"DNS\",\"DNS\",\"N\\\/A\",\"X\"],[\"SM 1\",\"88\",\"THOMAS CASAS\",\"15\",\"X\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"X\"],[\"SM 1\",\"73\",\"MICHAEL GAGNON\",\"X\",\"X\",\"X\",\"X\",\"14\",\"13\",\"N\\\/A\",\"X\"],[\"SM 1\",\"79\",\"MACKENZIE WEIL\",\"10\",\"X\",\"13\",\"X\",\"X\",\"X\",\"N\\\/A\",\"X\"],[\"SM 1\",\"112\",\"NICOLAS MEUNIER\",\"X\",\"X\",\"X\",\"X\",\"9\",\"DNF\",\"N\\\/A\",\"X\"],[\"SM 1\",\"151\",\"JC BERNIER\",\"X\",\"13\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"X\"],[\"SM 1\",\"34\",\"DAVE AUGER\",\"DNS\",\"X\",\"DNS\",\"X\",\"X\",\"X\",\"N\\\/A\",\"X\"],[\"SM 1\",\"X\",\"VINCENT WILSON\",\"DNS\",\"X\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"X\"],[\"SM 2\",\"158\",\"THOMAS LAROUCHE\",\"25\",\"22\",\"20\",\"25\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"22\"],[\"SM 2\",\"7\",\"THOMAS MANSEAU\",\"18\",\"25\",\"12\",\"20\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"20\"],[\"SM 2\",\"27\",\"JEREMIE LAUZI\\u00c8RE\",\"22\",\"12\",\"18\",\"18\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"16\"],[\"SM 2\",\"229\",\"ANTHONY GOSSELIN\",\"20\",\"16\",\"13\",\"13\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"14\"],[\"SM 2\",\"74\",\"CEDRICK BOURGEOIS TURCOTTE\",\"13\",\"10\",\"16\",\"12\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 2\",\"213\",\"JONATHAN MASSIGNANI\",\"15\",\"14\",\"11\",\"11\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"11\"],[\"SM 2\",\"38\",\"CHRIS BARTCZAK\",\"14\",\"15\",\"10\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 2\",\"177\",\"XAVIER MALTAIS\",\"10\",\"18\",\"14\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 2\",\"321\",\"DANY MARCHAND\",\"X\",\"X\",\"25\",\"22\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 2\",\"13\",\"ROMAIN LANGLET\",\"X\",\"X\",\"22\",\"16\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"15\"],[\"SM 2\",\"75\",\"FRANCK NUNES\",\"DNS\",\"20\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 2\",\"717\",\"TOMMY PAQUETTE\",\"16\",\"DNS\",\"DNS\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 2\",\"16\",\"SYLVAIN FOLLENFANT\",\"X\",\"X\",\"15\",\"14\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 2\",\"68\",\"JAGER STOCKILL\",\"DNS\",\"13\",\"DNS\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"25\"],[\"SM 2\",\"78\",\"CLAUDE SERGERIE\",\"12\",\"X\",\"DNS\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 2\",\"77\",\"ADRIEN JACQUART\",\"11\",\"11\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"13\"],[\"SM 2\",\"144\",\"MICHAEL BOURASSA\",\"X\",\"X\",\"X\",\"15\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"18\"],[\"SM 2\",\"14\",\"FRANCOIS DUMONT LAQUERRE\",\"X\",\"X\",\"X\",\"10\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"12\"],[\"SM 2\",\"11\",\"JOCELYN GAUTHIER\",\"X\",\"X\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 3\",\"40\",\"BEN BELLEVILLE\",\"15\",\"20\",\"22\",\"25\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"25\"],[\"SM 3\",\"10\",\"MARC DUMONT\",\"20\",\"22\",\"20\",\"20\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"15\"],[\"SM 3\",\"1\",\"CHELSEA PARENT-BOURSIER\",\"18\",\"25\",\"18\",\"10\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"13\"],[\"SM 3\",\"42\",\"DOMINIC ANDR\\u00c9-MONGEON\",\"X\",\"18\",\"25\",\"22\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"22\"],[\"SM 3\",\"281\",\"VALERIAN RENAUD\",\"13\",\"13\",\"16\",\"18\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"14\"],[\"SM 3\",\"97\",\"DYLAN SCOTT PAQUETTE\",\"12\",\"14\",\"15\",\"13\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"9\"],[\"SM 3\",\"51\",\"MARTIN PARENT\",\"16\",\"11\",\"13\",\"7\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"11\"],[\"SM 3\",\"404\",\"SHAWN ALEXIS GRONDIN GAUTHIER\",\"10\",\"15\",\"9\",\"9\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"DNS\"],[\"SM 3\",\"145\",\"GABRIEL ST-PIERRE\",\"8\",\"8\",\"11\",\"6\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"8\"],[\"SM 3\",\"321\",\"DANY MARCHAND\",\"25\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 3\",\"5\",\"PHILIPPE BOURDEAU\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"DNS\"],[\"SM 3\",\"16\",\"SYLVAIN FOLLENFANT\",\"22\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 3\",\"946\",\"ESTEBAN CANO\",\"X\",\"10\",\"10\",\"12\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"12\"],[\"SM 3\",\"393\",\"RAPHA\\u00cbL LEMIRE\",\"11\",\"X\",\"8\",\"5\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 3\",\"450\",\"ALEXIS MARINEAU\",\"DNS\",\"X\",\"DNS\",\"15\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"20\"],[\"SM 3\",\"17\",\"SALOMON H\\u00c9RARD\",\"X\",\"X\",\"14\",\"14\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"18\"],[\"SM 3\",\"11\",\"TOJO\",\"14\",\"X\",\"DNS\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 3\",\"29\",\"MARIO DUMAS\",\"DNS\",\"X\",\"12\",\"8\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"DNS\"],[\"SM 3\",\"70\",\"GUILLAUME REEVES VACHON\",\"9\",\"9\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 3\",\"85\",\"BEN GRAHAM\",\"7\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 3\",\"42\",\"PHILIPPE LUSIGNAN\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"DNS\"],[\"SM 3\",\"330\",\"ANTOINE GAGNON\",\"6\",\"X\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 3\",\"504\",\"DAVID BOURSIER\",\"X\",\"X\",\"X\",\"16\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 3\",\"104\",\"MAVRICK CYR\",\"X\",\"16\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 3\",\"33\",\"S\\u00c9BASTIEN LABELLE\",\"X\",\"12\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 3\",\"78\",\"JULIEN DUBOIS\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"10\"],[\"SM 3\",\"117\",\"ALEJANDRO RIVIERA\",\"X\",\"X\",\"X\",\"11\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"16\"],[\"SM 3\",\"21\",\"MAXIME BELLEROPHON\",\"X\",\"7\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 3\",\"533\",\"MARTIN VILLENEUVE\",\"DNS\",\"X\",\"DNS\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"DNS\"],[\"SM 3\",\"99\",\"BENOIT DRAPEAU\",\"X\",\"X\",\"DNS\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 3\",\"686\",\"FRED MONTMINY SAMSON\",\"X\",\"X\",\"DNS\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"DNS\"],[\"SM 3\",\"11\",\"GABRIEL CHARETTE\",\"X\",\"X\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 3\",\"482\",\"KEWINE SEDRICK\",\"X\",\"X\",\"DNS\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 3\",\"26\",\"ARNAUD BOURCIER\",\"DNS\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 3\",\"323\",\"PIERRE JUNIOR CHARRETTE\",\"DNS\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 4\",\"21\",\"XAVIER BELLEROPHON\",\"25\",\"25\",\"25\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"25\"],[\"SM 4\",\"321\",\"DANY MARCHAND\",\"22\",\"X\",\"22\",\"25\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 4\",\"38\",\"CHRIS BARTCZAK\",\"13\",\"16\",\"15\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"16\"],[\"SM 4\",\"292\",\"AUDREY FRAMAND\",\"14\",\"15\",\"14\",\"13\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 4\",\"78\",\"CLAUDE SERGERIE\",\"20\",\"X\",\"20\",\"14\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"18\"],[\"SM 4\",\"75\",\"FRANCK NUNES\",\"16\",\"22\",\"X\",\"16\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"DNS\"],[\"SM 4\",\"29\",\"MARIO DUMAS\",\"15\",\"14\",\"DNS\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"DNF\"],[\"SM 4\",\"9\",\"DIMITRI GODARD\",\"DNS\",\"X\",\"DNS\",\"22\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"22\"],[\"SM 4\",\"341\",\"DAVID BERTRAND\",\"X\",\"20\",\"X\",\"20\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 4\",\"51\",\"MARTIN PARENT\",\"18\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 4\",\"281\",\"VALERIAN RENAUD\",\"X\",\"X\",\"X\",\"18\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"20\"],[\"SM 4\",\"99\",\"BENOIT DRAPEAU\",\"X\",\"X\",\"18\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 4\",\"500\",\"PATRICE LABRECQUE\",\"X\",\"18\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 4\",\"124\",\"ALEX LAROUCHE\",\"X\",\"X\",\"16\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 4\",\"213\",\"JONATHAN MASSIGNANI\",\"X\",\"X\",\"X\",\"15\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"15\"],[\"SM 4\",\"533\",\"MARTIN VILLENEUVE\",\"X\",\"X\",\"DNS\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 4\",\"323\",\"PIERRE JUNIOR CHARRETTE\",\"DNS\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 4\",\"24\",\"YANNICK LAFLAMME\",\"DNS\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 5\",\"33\",\"S\\u00c9BASTIEN LABELLE\",\"25\",\"25\",\"25\",\"25\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"DNF\"],[\"SM 5\",\"367\",\"MARIO BERGERON\",\"22\",\"22\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 5\",\"35\",\"CL\\u00c9MENT LAROCHE\",\"DNF\",\"20\",\"X\",\"22\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"25\"],[\"SM 5\",\"42\",\"MAUDE DESCH\\u00c8NES\",\"X\",\"X\",\"DNS\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 5\",\"\",\"ARNAUD BOURCIER\",\"DNS\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 5\",\"29\",\"MARIO DUMAS\",\"DNS\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 6\",\"1\",\"CHELSEA PARENT-BOURSIER\",\"25\",\"25\",\"25\",\"25\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"DNS\"],[\"SM 6\",\"31\",\"M\\u00c9LODIE H\\u00c9BERT BOURASSA\",\"22\",\"18\",\"22\",\"22\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"25\"],[\"SM 6\",\"3\",\"ISABELLE G\\u00c9LINAS\",\"16\",\"22\",\"18\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 6\",\"X\",\"CLAUDINE OUELLET\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"22\"],[\"SM 6\",\"42\",\"MAUDE DESCH\\u00c8NES\",\"DNS\",\"X\",\"DNS\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 6\",\"292\",\"AUDREY FRAMAND\",\"13\",\"20\",\"20\",\"18\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 6\",\"22\",\"JULIE LALONDE\",\"15\",\"16\",\"16\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"18\"],[\"SM 6\",\"313\",\"SARAH MICHELLE COTTON\",\"20\",\"X\",\"DNS\",\"20\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"20\"],[\"SM 6\",\"28\",\"NATHALIE VEZEAU\",\"14\",\"15\",\"DNS\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 6\",\"77\",\"LISA CHABOT\",\"18\",\"DNS\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 6\",\"20\",\"KIM ISABELLE\",\"DNS\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 7\",\"2\",\"CHELSEA PARENT-BOURSIER\",\"DNS\",\"25\",\"25\",\"25\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 7\",\"21\",\"MAXIME BELLEROPHON\",\"X\",\"22\",\"20\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"18\"],[\"SM 7\",\"78\",\"JULIEN DUBOIS\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"20\"],[\"SM 7\",\"42\",\"JAYSON MONGEON\",\"X\",\"X\",\"X\",\"22\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 7\",\"17\",\"SALOMON H\\u00c9RARD\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"22\"],[\"SM 7\",\"68\",\"JAGER STOCKILL\",\"DNS\",\"X\",\"22\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"25\"],[\"SM 7\",\"124\",\"THOMAS LAROUCHE\",\"DNS\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"SM 8\",\"1\",\"OCTAVIA PINEL BOURSIER\",\"22\",\"18\",\"18\",\"20\",\"N\\\/A\",\"N\\\/A\",\"20\",\"DNS\"],[\"SM 8\",\"42\",\"JAYSON MONGEON\",\"X\",\"22\",\"22\",\"25\",\"N\\\/A\",\"N\\\/A\",\"25\",\"22\"],[\"SM 8\",\"66\",\"RYAN MORIN\",\"X\",\"20\",\"20\",\"22\",\"N\\\/A\",\"N\\\/A\",\"22\",\"18\"],[\"SM 8\",\"21\",\"MAXIME BELLEROPHON\",\"25\",\"25\",\"25\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"25\"],[\"SM 8\",\"244\",\"CHARLES MALTAIS\",\"14\",\"15\",\"16\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 8\",\"5\",\"JAYDEN VALL\\u00c9E\",\"X\",\"X\",\"X\",\"15\",\"N\\\/A\",\"N\\\/A\",\"18\",\"X\"],[\"SM 8\",\"117\",\"LOIC MALTAIS\",\"15\",\"16\",\"DNF\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 8\",\"913\",\"ABEL BYBERG\",\"20\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 8\",\"905\",\"ABBY DUBUC\",\"X\",\"X\",\"X\",\"18\",\"N\\\/A\",\"N\\\/A\",\"X\",\"20\"],[\"SM 8\",\"11\",\"ISAAC DUPUIS\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"16\"],[\"SM 8\",\"3\",\"CROSBY BYBERG\",\"18\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 8\",\"11\",\"MAGGIE BELLEVILLE\",\"X\",\"X\",\"X\",\"16\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 8\",\"89\",\"COLE HODGSON\",\"16\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 9\",\"1\",\"CHELSEA PARENT-BOURSIER\",\"25\",\"25\",\"25\",\"25\",\"N\\\/A\",\"N\\\/A\",\"25\",\"25\"],[\"SM 9\",\"31\",\"M\\u00c9LODIE H\\u00c9BERT BOURASSA\",\"22\",\"18\",\"16\",\"22\",\"N\\\/A\",\"N\\\/A\",\"22\",\"22\"],[\"SM 9\",\"22\",\"JULIE LALONDE\",\"15\",\"15\",\"14\",\"20\",\"N\\\/A\",\"N\\\/A\",\"20\",\"16\"],[\"SM 9\",\"33\",\"MARTINE BUSSI\\u00c8RE\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"13\"],[\"SM 9\",\"27\",\"GABRIELLE FUG\\u00c8RE\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"18\"],[\"SM 9\",\"439\",\"JULIE SIMARD\",\"20\",\"22\",\"20\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"DNF\",\"14\"],[\"SM 9\",\"313\",\"SARAH MICHELLE COTTON\",\"18\",\"X\",\"22\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"18\",\"15\"],[\"SM 9\",\"3\",\"ISABELLE G\\u00c9LINAS\",\"16\",\"16\",\"18\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"20\"],[\"SM 9\",\"292\",\"AUDREY FRAMAND\",\"13\",\"14\",\"13\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"12\"],[\"SM 9\",\"28\",\"NATHALIE VEZEAU\",\"14\",\"13\",\"DNS\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 9\",\"77\",\"LISA CHABOT\",\"DNS\",\"20\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 9\",\"42\",\"MAUDE DESCH\\u00c8NES\",\"DNS\",\"X\",\"15\",\"X\",\"N\\\/A\",\"N\\\/A\",\"DNF\",\"X\"],[\"SM 9\",\"99\",\"EMILY DICKSON\",\"DNS\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 9\",\"20\",\"KIM ISABELLE\",\"DNS\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"10\",\"MARC DUMONT\",\"16\",\"25\",\"25\",\"16\",\"N\\\/A\",\"N\\\/A\",\"18\",\"22\"],[\"SM 10\",\"78\",\"JULIEN DUBOIS\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"3\"],[\"SM 10\",\"1\",\"CHELSEA PARENT-BOURSIER\",\"11\",\"11\",\"11\",\"20\",\"N\\\/A\",\"N\\\/A\",\"22\",\"25\"],[\"SM 10\",\"281\",\"VALERIAN RENAUD\",\"7\",\"15\",\"14\",\"25\",\"N\\\/A\",\"N\\\/A\",\"13\",\"12\"],[\"SM 10\",\"51\",\"MARTIN PARENT\",\"15\",\"20\",\"12\",\"12\",\"N\\\/A\",\"N\\\/A\",\"12\",\"11\"],[\"SM 10\",\"404\",\"SHAWN ALEXIS GRONDIN GAUTHIER\",\"6\",\"16\",\"10\",\"10\",\"N\\\/A\",\"N\\\/A\",\"25\",\"20\"],[\"SM 10\",\"145\",\"GABRIEL ST-PIERRE\",\"1\",\"13\",\"22\",\"9\",\"N\\\/A\",\"N\\\/A\",\"16\",\"13\"],[\"SM 10\",\"450\",\"ALEXIS MARINEAU\",\"12\",\"X\",\"20\",\"22\",\"N\\\/A\",\"N\\\/A\",\"DNF\",\"18\"],[\"SM 10\",\"97\",\"DYLAN SCOTT PAQUETTE\",\"13\",\"1\",\"9\",\"14\",\"N\\\/A\",\"N\\\/A\",\"14\",\"4\"],[\"SM 10\",\"42\",\"DOMINIC ANDR\\u00c9-MONGEON\",\"X\",\"9\",\"6\",\"15\",\"N\\\/A\",\"N\\\/A\",\"20\",\"15\"],[\"SM 10\",\"29\",\"MARIO DUMAS\",\"DNS\",\"14\",\"16\",\"11\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"DNF\"],[\"SM 10\",\"11\",\"TOJO\",\"20\",\"X\",\"18\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"330\",\"ANTOINE GAGNON\",\"10\",\"22\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"946\",\"ESTEBAN CANO\",\"8\",\"4\",\"0\",\"18\",\"N\\\/A\",\"N\\\/A\",\"X\",\"8\"],[\"SM 10\",\"17\",\"SALOMON H\\u00c9RARD\",\"X\",\"X\",\"7\",\"8\",\"N\\\/A\",\"N\\\/A\",\"15\",\"9\"],[\"SM 10\",\"482\",\"KEWINE SEDRICK\",\"14\",\"X\",\"15\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"99\",\"BENOIT DRAPEAU\",\"X\",\"X\",\"13\",\"13\",\"N\\\/A\",\"N\\\/A\",\"X\",\"16\"],[\"SM 10\",\"321\",\"DANY MARCHAND\",\"25\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"40\",\"BEN BELLEVILLE\",\"22\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"33\",\"S\\u00c9BASTIEN LABELLE\",\"5\",\"3\",\"4\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"10\",\"2\"],[\"SM 10\",\"70\",\"GUILLAUME REEVES VACHON\",\"9\",\"12\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"197\",\"KEVIN PLOUFFE\",\"X\",\"18\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"16\",\"SYLVAIN FOLLENFANT\",\"18\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"35\",\"CL\\u00c9MENT LAROCHE\",\"3\",\"8\",\"X\",\"7\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"313\",\"SARAH MICHELLE COTTON\",\"0\",\"X\",\"3\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"9\",\"X\"],[\"SM 10\",\"45\",\"MIGUEL GAUDREAULT\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"11\",\"14\"],[\"SM 10\",\"922\",\"JEAN-PHILIPPE AUDET HALL\",\"DNS\",\"6\",\"5\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"DNS\"],[\"SM 10\",\"5\",\"PHILIPPE BOURDEAU\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"6\"],[\"SM 10\",\"23\",\"FELIX COTE\",\"X\",\"10\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"24\",\"YANNICK LAFLAMME\",\"DNS\",\"7\",\"2\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"405\",\"ERIC BELLEMARE\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"10\"],[\"SM 10\",\"393\",\"RAPHA\\u00cbL LEMIRE\",\"X\",\"X\",\"8\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"500\",\"PATRICE LABRECQUE\",\"X\",\"5\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"511\",\"SEBASTIEN BOUCHARD\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"5\"],[\"SM 10\",\"717\",\"TOMMY PAQUETTE\",\"4\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"533\",\"MARTIN VILLENEUVE\",\"DNS\",\"2\",\"0\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"1\"],[\"SM 10\",\"5\",\"MAX BROYDA\",\"2\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"686\",\"FRED MONTMINY SAMSON\",\"X\",\"X\",\"1\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"DNS\"],[\"SM 10\",\"292\",\"FR\\u00c9D\\u00c9RIC GAUTHIER DOYLE\",\"0\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"88\",\"MARC LABOSSI\\u00c8RE\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"X\"],[\"SM 10\",\"117\",\"ALEJANDRO RIVIERA\",\"X\",\"X\",\"X\",\"DNF\",\"N\\\/A\",\"N\\\/A\",\"X\",\"7\"],[\"SM 10\",\"59\",\"CLAUDE DENONCOURT\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"DNS\"],[\"SM 10\",\"11\",\"GABRIEL CHARETTE\",\"X\",\"X\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"26\",\"ARNAUD BOURCIER\",\"DNS\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"85\",\"BEN GRAHAM\",\"DNS\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"84\",\"BENJAMIN HODGSON\",\"DNS\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"140\",\"FRANCIS VALOIS\",\"DNS\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 10\",\"325\",\"PIERRE JUNIOR CHARRETTE\",\"DNS\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 11\",\"7\",\"THOMAS MANSEAU\",\"20\",\"25\",\"18\",\"25\",\"N\\\/A\",\"N\\\/A\",\"25\",\"18\"],[\"SM 11\",\"27\",\"JEREMIE LAUZI\\u00c8RE\",\"25\",\"18\",\"25\",\"18\",\"N\\\/A\",\"N\\\/A\",\"20\",\"20\"],[\"SM 11\",\"229\",\"ANTHONY GOSSELIN\",\"16\",\"22\",\"22\",\"16\",\"N\\\/A\",\"N\\\/A\",\"22\",\"25\"],[\"SM 11\",\"213\",\"JONATHAN MASSIGNANI\",\"14\",\"16\",\"14\",\"14\",\"N\\\/A\",\"N\\\/A\",\"18\",\"15\"],[\"SM 11\",\"16\",\"SYLVAIN FOLLENFANT\",\"X\",\"20\",\"20\",\"22\",\"N\\\/A\",\"N\\\/A\",\"X\",\"16\"],[\"SM 11\",\"158\",\"THOMAS LAROUCHE\",\"13\",\"14\",\"13\",\"20\",\"N\\\/A\",\"N\\\/A\",\"X\",\"13\"],[\"SM 11\",\"78\",\"CLAUDE SERGERIE\",\"12\",\"X\",\"9\",\"13\",\"N\\\/A\",\"N\\\/A\",\"16\",\"11\"],[\"SM 11\",\"38\",\"CHRIS BARTCZAK\",\"11\",\"12\",\"DNS\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"15\",\"12\"],[\"SM 11\",\"75\",\"FRANCK NUNES\",\"22\",\"15\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 11\",\"177\",\"XAVIER MALTAIS\",\"8\",\"13\",\"12\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 11\",\"13\",\"ROMAIN LANGLET\",\"X\",\"X\",\"15\",\"15\",\"N\\\/A\",\"N\\\/A\",\"X\",\"14\"],[\"SM 11\",\"717\",\"TOMMY PAQUETTE\",\"18\",\"DNS\",\"11\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"10\"],[\"SM 11\",\"74\",\"CEDRICK BOURGEOIS TURCOTTE\",\"15\",\"10\",\"DNF\",\"DNF\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 11\",\"77\",\"ADRIEN JACQUART\",\"10\",\"11\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"8\"],[\"SM 11\",\"321\",\"DANY MARCHAND\",\"X\",\"X\",\"16\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 11\",\"14\",\"FRANCOIS DUMONT LAQUERRE\",\"X\",\"X\",\"X\",\"12\",\"N\\\/A\",\"N\\\/A\",\"X\",\"9\"],[\"SM 11\",\"68\",\"JAGER STOCKILL\",\"DNS\",\"DNS\",\"10\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"22\"],[\"SM 11\",\"88R\",\"LAURENT LALIBERT\\u00c9 GIRARD\",\"9\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 12\",\"24\",\"S\\u00c9BASTIEN TREMBLAY\",\"22\",\"25\",\"18\",\"25\",\"N\\\/A\",\"N\\\/A\",\"25\",\"22\"],[\"SM 12\",\"93\",\"ALEX DESHAIES\",\"15\",\"20\",\"20\",\"20\",\"N\\\/A\",\"N\\\/A\",\"22\",\"25\"],[\"SM 12\",\"1\",\"NELSON JR MOREAU\",\"25\",\"22\",\"25\",\"22\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 12\",\"777\",\"ZAIM LAFLAMME\",\"18\",\"16\",\"22\",\"15\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 12\",\"31\",\"PAUL-\\u00c9TIENNE COURTOIS\",\"20\",\"15\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"14\",\"X\"],[\"SM 12\",\"21\",\"XAVIER BELLEROPHON\",\"14\",\"18\",\"16\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"16\"],[\"SM 12\",\"9\",\"DIMITRI GODARD\",\"16\",\"X\",\"15\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"16\",\"18\"],[\"SM 12\",\"104\",\"MAVRICK CYR\",\"X\",\"X\",\"14\",\"14\",\"N\\\/A\",\"N\\\/A\",\"18\",\"20\"],[\"SM 12\",\"654\",\"JACOB BILODEAU\",\"13\",\"X\",\"13\",\"18\",\"N\\\/A\",\"N\\\/A\",\"X\",\"15\"],[\"SM 12\",\"183\",\"DAVID POTHIER\",\"X\",\"X\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 12\",\"67\",\"JIMMY MORIN\",\"X\",\"14\",\"12\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"15\",\"X\"],[\"SM 12\",\"44\",\"GABRIEL VALL\\u00c9E\",\"X\",\"X\",\"X\",\"16\",\"N\\\/A\",\"N\\\/A\",\"20\",\"X\"],[\"SM 12\",\"341\",\"DAVID BERTRAND\",\"X\",\"X\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 12\",\"94\",\"LUDOVICK S\\u00c9VIGNY\",\"12\",\"X\",\"11\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 12\",\"87\",\"DAVID GUERTIN\",\"11\",\"X\",\"9\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 12\",\"79\",\"MACKENZIE WEIL\",\"DNS\",\"X\",\"10\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 12\",\"88\",\"VINCENT WILSON\",\"10\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 12\",\"3\",\"MARTIN CARPENTIER\",\"9\",\"X\",\"X\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"SM 12\",\"777\",\"JEAN-FRAN\\u00c7OIS CYR\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"X\"],[\"SM 12\",\"34\",\"DAVE AUGER\",\"DNS\",\"X\",\"DNS\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"LW\",\"222\",\"CHELSEA PARENT-BOURSIER\",\"20\",\"22\",\"22\",\"22\",\"N\\\/A\",\"N\\\/A\",\"25\",\"22\"],[\"LW\",\"22\",\"ZAIM LAFLAMME\",\"18\",\"25\",\"25\",\"25\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"LW\",\"145\",\"GABRIEL ST-PIERRE\",\"16\",\"15\",\"16\",\"20\",\"N\\\/A\",\"N\\\/A\",\"20\",\"20\"],[\"LW\",\"24\",\"YANNICK LAFLAMME\",\"13\",\"18\",\"15\",\"18\",\"N\\\/A\",\"N\\\/A\",\"16\",\"15\"],[\"LW\",\"29\",\"AUDREY FRAMAND\",\"8\",\"12\",\"13\",\"16\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"X\"],[\"LW\",\"922\",\"JEAN-PHILIPPE AUDET HALL\",\"14\",\"16\",\"18\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"X\",\"16\"],[\"LW\",\"717\",\"TOMMY PAQUETTE\",\"22\",\"DNS\",\"20\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"25\"],[\"LW\",\"49\",\"BENOIT B\\u00c9LAIR\",\"15\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"14\",\"18\"],[\"LW\",\"27\",\"GABRIELLE FUG\\u00c8RE\",\"X\",\"13\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"15\",\"14\"],[\"LW\",\"439\",\"JULIE SIMARD\",\"12\",\"DNS\",\"14\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"DNF\"],[\"LW\",\"313\",\"DIMITRI GODARD\",\"25\",\"X\",\"DNS\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"DNS\",\"X\"],[\"LW\",\"292\",\"FR\\u00c9D\\u00c9RIC GAUTHIER DOYLE\",\"9\",\"14\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"LW\",\"22\",\"MAVRICK CYR\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"22\",\"DNS\"],[\"LW\",\"95\",\"ALEXIS BAHL\",\"X\",\"20\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"LW\",\"760\",\"WILLIAM GENEST\",\"X\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"18\",\"X\"],[\"LW\",\"21\",\"MAXIME BELLEROPHON\",\"11\",\"DNS\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"LW\",\"99\",\"EMILY DICKSON\",\"10\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"LW\",\"34\",\"DAVE AUGER\",\"X\",\"X\",\"DNS\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"LW\",\"52\",\"THIBAULT GILBERT\",\"X\",\"DNS\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"LW\",\"222V\",\"AARON MEAD\",\"DNF\",\"X\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"X\",\"X\"],[\"MINI MOTO 1\",\"16\",\"SYLVAIN FOLLENFANT\",\"25\",\"N\\\/A\",\"22\",\"25\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"20\"],[\"MINI MOTO 1\",\"213\",\"JONATHAN MASSIGNANI\",\"22\",\"N\\\/A\",\"25\",\"22\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"22\"],[\"MINI MOTO 1\",\"51\",\"MARTIN PARENT\",\"18\",\"N\\\/A\",\"20\",\"20\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"16\"],[\"MINI MOTO 1\",\"42\",\"PHILIPPE LUSIGNAN\",\"X\",\"N\\\/A\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"18\"],[\"MINI MOTO 1\",\"84\",\"BENJAMIN HODGSON\",\"20\",\"N\\\/A\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"MINI MOTO 1\",\"68\",\"JAGER STOCKILL\",\"DNS\",\"N\\\/A\",\"DNF\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"25\"],[\"MINI MOTO 2\",\"222\",\"CHELSEA PARENT-BOURSIER\",\"25\",\"N\\\/A\",\"25\",\"25\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"20\"],[\"MINI MOTO 2\",\"22\",\"JULIE LALONDE\",\"16\",\"N\\\/A\",\"20\",\"22\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"16\"],[\"MINI MOTO 2\",\"21\",\"MAXIME BELLEROPHON\",\"22\",\"N\\\/A\",\"22\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"18\"],[\"MINI MOTO 2\",\"85\",\"BEN GRAHAM\",\"20\",\"N\\\/A\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"MINI MOTO 2\",\"5\",\"PHILIPPE BOURDEAU\",\"X\",\"N\\\/A\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"25\"],[\"MINI MOTO 2\",\"313\",\"SARAH MICHELLE COTTON\",\"X\",\"N\\\/A\",\"18\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"15\"],[\"MINI MOTO 2\",\"27\",\"COHEN BYBERG\",\"18\",\"N\\\/A\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"MINI MOTO 2\",\"222V\",\"AARON MEAD\",\"DNS\",\"N\\\/A\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"MINI MOTO 3\",\"686\",\"FRED MONTMINY SAMSON\",\"X\",\"N\\\/A\",\"22\",\"25\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"25\"],[\"MINI MOTO 3\",\"59\",\"CLAUDE DENONCOURT\",\"X\",\"N\\\/A\",\"25\",\"22\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"MINI MOTO 3\",\"29\",\"AUDREY FRAMAND\",\"20\",\"N\\\/A\",\"20\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"15\"],[\"MINI MOTO 3\",\"42\",\"JAYSON MONGEON\",\"X\",\"N\\\/A\",\"18\",\"20\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"20\"],[\"MINI MOTO 3\",\"66\",\"RYAN MORIN\",\"X\",\"N\\\/A\",\"14\",\"16\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"22\"],[\"MINI MOTO 2\",\"149\",\"PATRICK PRATTE\",\"X\",\"N\\\/A\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"22\"],[\"MINI MOTO 3\",\"1\",\"OCTAVIA PINEL BOURSIER\",\"X\",\"N\\\/A\",\"15\",\"15\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"16\"],[\"MINI MOTO 3\",\"313\",\"SARAH MICHELLE COTTON\",\"25\",\"N\\\/A\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"MINI MOTO 3\",\"11\",\"DEACON HOFFMAN\",\"22\",\"N\\\/A\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"MINI MOTO 3\",\"905\",\"ABBY DUBUC\",\"X\",\"N\\\/A\",\"X\",\"18\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"18\"],[\"MINI MOTO 3\",\"28\",\"NATHALIE VEZEAU\",\"18\",\"N\\\/A\",\"DNS\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"MINI MOTO 3\",\"439\",\"JULIE SIMARD\",\"X\",\"N\\\/A\",\"16\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"MINI MOTO 3\",\"913\",\"ABEL BYBERG\",\"16\",\"N\\\/A\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"MINI MOTO 3\",\"33\",\"MARTINE BUSSI\\u00c8RE\",\"X\",\"N\\\/A\",\"DNS\",\"DNS\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"MINI MOTO 4\",\"1\",\"OCTAVIA PINEL BOURSIER\",\"22\",\"N\\\/A\",\"18\",\"18\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"15\"],[\"MINI MOTO 4\",\"42\",\"JAYSON MONGEON\",\"X\",\"N\\\/A\",\"22\",\"25\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"22\"],[\"MINI MOTO 4\",\"66\",\"RYAN MORIN\",\"X\",\"N\\\/A\",\"20\",\"20\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"20\"],[\"MINI MOTO 4\",\"21\",\"MAXIME BELLEROPHON\",\"X\",\"N\\\/A\",\"25\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"25\"],[\"MINI MOTO 4\",\"19\",\"EVERLY LANDRY\",\"25\",\"N\\\/A\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"MINI MOTO 4\",\"905\",\"ABBY DUBUC\",\"X\",\"N\\\/A\",\"X\",\"22\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"18\"],[\"MINI MOTO 4\",\"3\",\"CROSBY BYBERG\",\"20\",\"N\\\/A\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"MINI MOTO 4\",\"28\",\"NATALIA TANASIC\",\"18\",\"N\\\/A\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"MINI MOTO 4\",\"5\",\"EMY JADE VALL\\u00c9E\",\"X\",\"N\\\/A\",\"X\",\"16\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"X\"],[\"MINI MOTO 4\",\"11\",\"ISAAC DUPUIS\",\"X\",\"N\\\/A\",\"X\",\"X\",\"N\\\/A\",\"N\\\/A\",\"N\\\/A\",\"16\"]]},\"formats\":[],\"gala\":[[\"SEASON\",\"CODE\",\"PUBLIC\",\"GALA CLASS ORDER\"],[\"2026\",\"\\u2022\",\"2026-10-10 22:00\",\"MINI MOTO 4, MINI MOTO 3, MINI MOTO 2, MINI MOTO 1, LW, SM 9, SM 10, SM 11, SM 12, SM 8, SM 7, SM 6, SM 5, SM 4, SM 3, SM 2, SM 1, SBK 4, SBK 3, SS 2, SBK 2, SS 1, SBK 1\"]],\"highlights\":[[\"SEASON\",\"CODE\",\"TYPE\",\"SHOW\",\"TROPHY\",\"VALUE\",\"NAMES\",\"TITLE EN\",\"TITLE FR\",\"UNIT EN\",\"UNIT FR\",\"DETAIL EN\",\"DETAIL FR\",\"EXPLANATION EN\",\"EXPLANATION FR\"],[\"2026\",\"TITLES\",\"COMPUTED\",\"YES\"],[\"2026\",\"WINS\",\"COMPUTED\",\"YES\"],[\"2026\",\"PODIUMS\",\"COMPUTED\",\"YES\"],[\"2026\",\"PERFECT\",\"COMPUTED\",\"YES\"],[\"2026\",\"TIGHT\",\"COMPUTED\",\"YES\"],[\"2026\",\"WIDE\",\"COMPUTED\",\"YES\"],[\"2026\",\"IRON\",\"COMPUTED\",\"YES\"],[\"2026\",\"CLASSES\",\"COMPUTED\",\"YES\"],[\"2026\",\"FIELD\",\"COMPUTED\",\"YES\"],[\"2026\",\"VERSATILE\",\"CUSTOM\",\"YES\",\"YES\",\"\",\"CHELSEA PARENT-BOURSIER\",\"Most versatile rider\",\"Pilote le plus versatile\",\"\",\"\",\"At home on every track, in every category, and on every platform.\",\"\\u00c0 l\\u2019aise sur toutes les pistes, dans toutes les cat\\u00e9gories et sur toutes les plateformes.\",\"A versatile rider who adapts effortlessly to any track, racing category, or platform. Whatever the challenge, she quickly finds her rhythm and performs with confidence.\",\"Une pilote polyvalente qui s\\u2019adapte facilement \\u00e0 toutes les pistes, toutes les cat\\u00e9gories et toutes les plateformes. Peu importe le d\\u00e9fi, elle trouve rapidement son rythme et roule avec confiance.\"],[\"2026\",\"CORIACE\",\"CUSTOM\",\"YES\",\"YES\",\"\",\"AUDREY FRAMAND\",\"Toughest rider\",\"La pilote le plus coriace\",\"\",\"\",\"A rider who never gives up. Always there, always ready to take the start, despite the crashes, the injuries, and the tough times. She gets back up, grits her teeth, and keeps moving forward with courage and determination.\\n\\nBecause being tough isn\\u2019t about never falling\\u2026 it\\u2019s about always getting back up.\",\"Une pilote qui ne baisse jamais les bras. Toujours pr\\u00e9sente, toujours pr\\u00eate \\u00e0 prendre le d\\u00e9part, malgr\\u00e9 les chutes, les blessures et les moments difficiles. Elle se rel\\u00e8ve, serre les dents et continue d\\u2019avancer avec courage et d\\u00e9termination.\\n\\nParce qu\\u2019\\u00eatre coriace, ce n\\u2019est pas de ne jamais tomber\\u2026 c\\u2019est de toujours se relever.\",\"A resilient rider who never gives up. No matter the crashes, injuries, or setbacks, she always finds the strength to get back up, return to the starting line, and keep pushing forward.\",\"Une pilote r\\u00e9siliente qui n\\u2019abandonne jamais. Malgr\\u00e9 les chutes, les blessures et les moments difficiles, elle trouve toujours la force de se relever, de reprendre le d\\u00e9part et de continuer \\u00e0 avancer.\"],[\"2026\",\"RECRUE\",\"CUSTOM\",\"YES\",\"YES\",\"1\",\"MAXIME BELLEROPHON\",\"Rookie of the year\",\"Recrue de l'ann\\u00e9e\",\"season\",\"saison\",\"A first season full of promise.\",\"Une premi\\u00e8re saison pleine de promesses.\",\"A promising first season, showing great potential and laying the foundation for an exciting future on the track.\",\"Une premi\\u00e8re saison prometteuse, qui r\\u00e9v\\u00e8le un beau potentiel et pose les bases d\\u2019un avenir emballant sur la piste.\"],[\"2026\",\"RECRUE-PRO-SBK\",\"CUSTOM\",\"YES\",\"YES\",\"1\",\"JONATHAN MASSIGNANI\",\"Pro rookie in SBK\",\"Recrue pro en SBK\",\"season\",\"saison\",\"A first pro season in Superbike, right on the pace.\",\"Une premi\\u00e8re saison pro en Superbike, d\\u00e9j\\u00e0 dans le rythme.\",\"A strong first professional season in Superbike, quickly finding the pace and proving she belongs among the competition.\",\"Une solide premi\\u00e8re saison professionnelle en Superbike, trouvant rapidement le rythme et d\\u00e9montrant qu\\u2019elle a sa place parmi les meilleurs.\"],[\"2026\",\"RECRUE-PRO-SM\",\"CUSTOM\",\"YES\",\"YES\",\"1\",\"ALEX DESHAIES\",\"Pro rookie in SM\",\"Recrue pro en SM\",\"season\",\"saison\",\"A first pro season in Supermoto, right on the pace.\",\"Une premi\\u00e8re saison pro en Supermoto, d\\u00e9j\\u00e0 dans le rythme.\",\"A strong first professional season in Supermoto, quickly finding the pace and proving she belongs among the competition.\",\"Une solide premi\\u00e8re saison professionnelle en Supermoto, trouvant rapidement le rythme et d\\u00e9montrant qu\\u2019elle a sa place parmi les meilleurs.\"]],\"sponsors\":[[\"SEASON\",\"NAME\",\"LOGO\",\"TIER\",\"SINCE\"],[\"2026\",\"LES \\u00c9QUIPEMENTS D.B.\",\"equipements-db.webp\",\"PLATINUM\"],[\"2026\",\"Motosport 4 Saisons\",\"motosport-4-saisons.webp\",\"PLATINUM\"],[\"2026\",\"ADM Sport\",\"adm-sport.webp\",\"GOLD\"],[\"2026\",\"MAXIMUM LOCATION\",\"maximum.webp\",\"PLATINUM\"],[\"2026\",\"Dirt-Care\",\"dirt-care.webp\",\"GOLD\"],[\"2026\",\"X-Track\",\"x-track.webp\",\"PLATINUM\"],[\"2026\",\"BG Print\",\"bg-print.webp\",\"PLATINUM\"],[\"2026\",\"CPDG\",\"cpdg.webp\",\"PLATINUM\"],[\"2026\",\"Luc Carpentier et fils\",\"luc-carpentier-et-fils.webp\",\"PLATINUM\"],[\"2026\",\"JM Moto\",\"jm-moto.webp\",\"PLATINUM\"],[\"2026\",\"Coffrage Pelletier\",\"coffrage-pelletier.webp\",\"PLATINUM\"],[\"2026\",\"Remorkoto\",\"remorkoto.webp\",\"PLATINUM\"],[\"2026\",\"Lemarier Design\",\"lemarier-design.webp\",\"PLATINUM\"],[\"2026\",\"88Moto\",\"88moto.webp\",\"PLATINUM\"],[\"2026\",\"MaxTec\",\"maxtec.webp\",\"PLATINUM\"],[\"2026\",\"CANOTS RH\\u00c9AUME\",\"rheaume-canots.webp\",\"PLATINUM\"],[\"2026\",\"Mathias\",\"mathias.webp\",\"PLATINUM\"],[\"2026\",\"Fuel Factory\",\"fuel-factory.webp\",\"GOLD\"],[\"2026\",\"Sport Collette Rive-Sud\",\"sport-collette-rive-sud.webp\",\"GOLD\"],[\"2026\",\"Morin Sports \\u0026 V.R.\",\"morin-sports-v-r.webp\",\"GOLD\"],[\"2026\",\"DMS RaceShop\",\"dms-raceshop.webp\",\"GOLD\"],[\"2026\",\"\\u00c9cole de moto de la Capitale\",\"ecole-de-moto-de-la-capitale.webp\",\"GOLD\"],[\"2026\",\"Bob's Shop\",\"bob-s-shop.webp\",\"GOLD\"],[\"2026\",\"Line-X Trois-Rivi\\u00e8res\",\"line-x-3r.webp\",\"GOLD\"],[\"2026\",\"Pete Moto Racing Team\",\"pete-moto-racing-team.webp\",\"GOLD\"],[\"2026\",\"SIC BOLTS\",\"sc-bolts.webp\",\"SILVER\"],[\"2026\",\"ST Moto Sport\",\"st-moto-sport.webp\",\"GOLD\"],[\"2026\",\"Atelier M\\u00e9canique Deshaies\",\"atelier-mecanique-deshaies.webp\",\"SILVER\"],[\"2026\",\"YCF\",\"ycf.webp\",\"GOLD\"],[\"2026\",\"MOTO ILLIMIT\\u00c9\",\"groupe-mi.webp\",\"SILVER\"],[\"2026\",\"Dupont\",\"dupont.webp\",\"BRONZE\"],[\"2026\",\"Massoth\\u00e9rapie de la Capitaine\",\"massotherapie-de-la-capitaine.webp\",\"SILVER\"],[\"2026\",\"Big Willy\",\"big-willy.webp\",\"BRONZE\"],[\"2026\",\"Gabriel Charette Services financiers\",\"gabriel-charette-services-financiers.webp\",\"GOLD\"],[\"2026\",\"A-1 Tirage de joints\",\"a-1-tirage-de-joints.webp\",\"GOLD\"],[\"2026\",\"Remorquage JRM\",\"remorquage-jrm.webp\",\"PLATINUM\"],[\"2026\",\"Houle Asphalte\",\"houle-asphalte.webp\",\"BRONZE\"],[\"2026\",\"Conception C3M\",\"conception-c3m.webp\",\"BRONZE\"],[\"2026\",\"MFI9\",\"mfi9.webp\",\"SILVER\"],[\"2026\",\"Labelle M\\u00e9canique\",\"labelle-mecanique.webp\",\"SILVER\"],[\"2026\",\"Duroy Motosport\",\"duroy-motosport.webp\",\"SILVER\"],[\"2026\",\"MARC COSSETTE \\u00c9LECTRICIEN\",\"marc-cossette.webp\",\"SILVER\"],[\"2026\",\"ProTekSport\",\"proteksport.webp\",\"BRONZE\"],[\"2026\",\"Forestier S. Dupont\",\"forestier-s-dupont.webp\",\"BRONZE\"],[\"2026\",\"Apex Motos\",\"apex-motos.webp\",\"SILVER\"],[\"2026\",\"Firstline Graphics\",\"firstline-graphics.webp\",\"BRONZE\"],[\"2026\",\"Dynamic Suspension\",\"dynamic-suspension.webp\",\"BRONZE\"],[\"2026\",\"OSCAR SPEEDSHOP\",\"car-speedshop.webp\",\"BRONZE\"],[\"2026\",\"Moto MPH\",\"moto-mph.webp\",\"GOLD\"],[\"2026\",\"Ride or Dry\",\"ride-or-dry.webp\",\"BRONZE\"],[\"2026\",\"DP Multi-M\\u00e9canic\",\"dp-multi-mecanic.webp\",\"BRONZE\"],[\"2026\",\"NAPA Autopro DV Auto\",\"napa-autopro-dv-auto.webp\",\"BRONZE\"],[\"2026\",\"Beringer Canada\",\"beringer-canada.webp\",\"BRONZE\"],[\"2026\",\"Moto MST\",\"moto-mst.webp\",\"BRONZE\"],[\"2026\",\"Steel Pot\",\"steel-pot.webp\",\"BRONZE\"]],\"sponsorLogos\":{\"271-asselin-racing.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/271-asselin-racing.webp?v=1b371f1179\",\"w\":2000,\"h\":584,\"ink\":0.5300000000000000266453525910037569701671600341796875,\"plate\":\"#ffffff\"},\"88moto.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/88moto.webp?v=a7ce7fb3f4\",\"w\":2000,\"h\":397,\"ink\":0.61199999999999998845368054389837197959423065185546875,\"plate\":\"#000000\"},\"a-1-tirage-de-joints.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/a-1-tirage-de-joints.webp?v=7798342558\",\"w\":2000,\"h\":619,\"ink\":0.24699999999999999733546474089962430298328399658203125,\"plate\":\"#000000\"},\"adm-sport.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/adm-sport.webp?v=45d9add594\",\"w\":2000,\"h\":339,\"ink\":0.3449999999999999733546474089962430298328399658203125,\"plate\":\"#000000\"},\"apex-motos.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/apex-motos.webp?v=cefecce194\",\"w\":2000,\"h\":650,\"ink\":0.49299999999999999378275106209912337362766265869140625,\"plate\":\"#000000\"},\"atelier-mecanique-deshaies.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/atelier-mecanique-deshaies.webp?v=52f02985c1\",\"w\":2000,\"h\":507,\"ink\":0.1600000000000000033306690738754696212708950042724609375,\"plate\":\"#000000\"},\"beringer-canada.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/beringer-canada.webp?v=425cf930d9\",\"w\":1768,\"h\":664,\"ink\":0.59799999999999997601918266809661872684955596923828125,\"plate\":\"#000000\"},\"bg-print.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/bg-print.webp?v=d53adad297\",\"w\":771,\"h\":800,\"ink\":0.417999999999999982680520815847557969391345977783203125,\"plate\":\"#000000\"},\"big-willy.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/big-willy.webp?v=be2e05c118\",\"w\":788,\"h\":800,\"ink\":0.677000000000000046185277824406512081623077392578125,\"plate\":\"#000000\"},\"bob-s-shop.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/bob-s-shop.webp?v=5b0077b476\",\"w\":2000,\"h\":769,\"ink\":0.38000000000000000444089209850062616169452667236328125,\"plate\":\"#000000\"},\"car-speedshop.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/car-speedshop.webp?v=ffb2f56de9\",\"w\":1808,\"h\":756,\"ink\":0.3489999999999999769073610877967439591884613037109375,\"plate\":\"#000000\"},\"coffrage-pelletier.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/coffrage-pelletier.webp?v=5c3f92c32e\",\"w\":2000,\"h\":573,\"ink\":0.4859999999999999875655021241982467472553253173828125,\"plate\":\"#000000\"},\"conception-c3m.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/conception-c3m.webp?v=f2abf041c4\",\"w\":1416,\"h\":800,\"ink\":0.297999999999999987121412914348184131085872650146484375,\"plate\":\"#000000\"},\"cpdg.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/cpdg.webp?v=f5d4817dcd\",\"w\":2000,\"h\":766,\"ink\":0.433999999999999996891375531049561686813831329345703125,\"plate\":\"#000000\"},\"deboisement-hp.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/deboisement-hp.webp?v=98f117801d\",\"w\":994,\"h\":800,\"ink\":0.7119999999999999662492200513952411711215972900390625,\"plate\":\"#ffffff\"},\"dirt-care.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/dirt-care.webp?v=02d78e4a26\",\"w\":934,\"h\":800,\"ink\":0.2099999999999999922284388276239042170345783233642578125,\"plate\":\"#000000\"},\"dms-raceshop.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/dms-raceshop.webp?v=dd20c847f9\",\"w\":2000,\"h\":302,\"ink\":0.278000000000000024868995751603506505489349365234375,\"plate\":\"#000000\"},\"dp-multi-mecanic.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/dp-multi-mecanic.webp?v=4262a68b26\",\"w\":1252,\"h\":800,\"ink\":0.412999999999999978239628717346931807696819305419921875,\"plate\":\"#000000\"},\"dupont.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/dupont.webp?v=f714069e5c\",\"w\":1778,\"h\":800,\"ink\":0.54700000000000004174438572590588591992855072021484375,\"plate\":\"#000000\"},\"duroy-motosport.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/duroy-motosport.webp?v=46ccc897d3\",\"w\":1190,\"h\":800,\"ink\":0.62800000000000000266453525910037569701671600341796875,\"plate\":\"#000000\"},\"dynamic-suspension.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/dynamic-suspension.webp?v=37d36ede83\",\"w\":1800,\"h\":636,\"ink\":0.5140000000000000124344978758017532527446746826171875,\"plate\":\"#000000\"},\"ecole-de-moto-de-la-capitale.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/ecole-de-moto-de-la-capitale.webp?v=14af7c1c1d\",\"w\":1404,\"h\":800,\"ink\":0.2089999999999999913402604079237789846956729888916015625,\"plate\":\"#000000\"},\"equipements-db.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/equipements-db.webp?v=89f92a7358\",\"w\":2000,\"h\":646,\"ink\":0.27900000000000002575717417130363173782825469970703125,\"plate\":\"#000000\"},\"excavation-mathieu-gauthier.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/excavation-mathieu-gauthier.webp?v=39cccd14d5\",\"w\":1581,\"h\":800,\"ink\":0.313000000000000000444089209850062616169452667236328125,\"plate\":\"#000000\"},\"firstline-graphics.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/firstline-graphics.webp?v=39a76f0a7e\",\"w\":2000,\"h\":307,\"ink\":0.283999999999999974686915038546430878341197967529296875,\"plate\":\"#000000\"},\"forestier-s-dupont.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/forestier-s-dupont.webp?v=077dc35a38\",\"w\":2000,\"h\":503,\"ink\":0.316000000000000003108624468950438313186168670654296875,\"plate\":\"#000000\"},\"fuel-factory.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/fuel-factory.webp?v=c149851c02\",\"w\":2000,\"h\":283,\"ink\":0.36799999999999999378275106209912337362766265869140625,\"plate\":\"#000000\"},\"gabriel-charette-services-financiers.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/gabriel-charette-services-financiers.webp?v=96a09a0a30\",\"w\":2000,\"h\":800,\"ink\":0.319000000000000005773159728050814010202884674072265625,\"plate\":\"#000000\"},\"godcher.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/godcher.webp?v=81f98b3db8\",\"w\":2000,\"h\":677,\"ink\":0.411999999999999977351450297646806575357913970947265625,\"plate\":\"#ffffff\"},\"groupe-mi.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/groupe-mi.webp?v=e9a2770d23\",\"w\":1748,\"h\":660,\"ink\":0.424999999999999988897769753748434595763683319091796875,\"plate\":\"#000000\"},\"houle-asphalte.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/houle-asphalte.webp?v=61ab885a06\",\"w\":1549,\"h\":800,\"ink\":0.54300000000000003819167204710538499057292938232421875,\"plate\":\"#000000\"},\"idle.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/idle.webp?v=80aa7fbaef\",\"w\":882,\"h\":800,\"ink\":0.2399999999999999911182158029987476766109466552734375,\"plate\":\"#ffffff\"},\"jm-moto.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/jm-moto.webp?v=b544c59f54\",\"w\":1924,\"h\":800,\"ink\":0.461000000000000020872192862952942959964275360107421875,\"plate\":\"#000000\"},\"kyt.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/kyt.webp?v=80c7aab1c8\",\"w\":1456,\"h\":800,\"ink\":0.5020000000000000017763568394002504646778106689453125,\"plate\":\"#ffffff\"},\"labelle-mecanique.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/labelle-mecanique.webp?v=1913103cfd\",\"w\":2000,\"h\":713,\"ink\":0.50500000000000000444089209850062616169452667236328125,\"plate\":\"#000000\"},\"lemarier-design.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/lemarier-design.webp?v=681771c616\",\"w\":2000,\"h\":576,\"ink\":0.487999999999999989341858963598497211933135986328125,\"plate\":\"#000000\"},\"line-x-3r.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/line-x-3r.webp?v=3e0a01433b\",\"w\":1485,\"h\":800,\"ink\":0.5480000000000000426325641456060111522674560546875,\"plate\":\"#000000\"},\"luc-carpentier-et-fils.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/luc-carpentier-et-fils.webp?v=450f2b3515\",\"w\":1672,\"h\":800,\"ink\":0.176999999999999990674126593148685060441493988037109375,\"plate\":\"#000000\"},\"marc-cossette.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/marc-cossette.webp?v=90f39f3a7a\",\"w\":1240,\"h\":800,\"ink\":0.370999999999999996447286321199499070644378662109375,\"plate\":\"#000000\"},\"massotherapie-de-la-capitaine.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/massotherapie-de-la-capitaine.webp?v=6403f07f5a\",\"w\":1489,\"h\":800,\"ink\":0.0970000000000000028865798640254070051014423370361328125,\"plate\":\"#000000\"},\"mathias.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/mathias.webp?v=4beed3f8fc\",\"w\":2000,\"h\":564,\"ink\":0.319000000000000005773159728050814010202884674072265625,\"plate\":\"#000000\"},\"maximum.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/maximum.webp?v=e73bbb517f\",\"w\":2000,\"h\":321,\"ink\":0.51700000000000001509903313490212894976139068603515625,\"plate\":\"#000000\"},\"maxtec.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/maxtec.webp?v=0799365d80\",\"w\":2000,\"h\":392,\"ink\":0.2439999999999999946709294817992486059665679931640625,\"plate\":\"#000000\"},\"mecaglisse.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/mecaglisse.webp?v=1dee56676f\",\"w\":868,\"h\":424,\"ink\":0.197000000000000008437694987151189707219600677490234375,\"plate\":\"#ffffff\"},\"mfi9.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/mfi9.webp?v=8ce8e071a3\",\"w\":767,\"h\":800,\"ink\":0.51300000000000001154631945610162802040576934814453125,\"plate\":\"#000000\"},\"mini-cup-canada.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/mini-cup-canada.webp?v=dfe1358402\",\"w\":1165,\"h\":800,\"ink\":0.54700000000000004174438572590588591992855072021484375,\"plate\":\"#ffffff\"},\"mini-moto-3r.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/mini-moto-3r.webp?v=2b8745460a\",\"w\":1549,\"h\":800,\"ink\":0.278000000000000024868995751603506505489349365234375,\"plate\":\"#ffffff\"},\"morin-sports-v-r.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/morin-sports-v-r.webp?v=14316b82bd\",\"w\":1769,\"h\":800,\"ink\":0.254000000000000003552713678800500929355621337890625,\"plate\":\"#000000\"},\"moto-mph.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/moto-mph.webp?v=cc53795305\",\"w\":1104,\"h\":800,\"ink\":0.435999999999999998667732370449812151491641998291015625,\"plate\":\"#000000\"},\"moto-mst.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/moto-mst.webp?v=9565c7f077\",\"w\":1500,\"h\":508,\"ink\":0.560000000000000053290705182007513940334320068359375,\"plate\":\"#000000\"},\"motosport-4-saisons.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/motosport-4-saisons.webp?v=f8e1853c98\",\"w\":2000,\"h\":371,\"ink\":0.37399999999999999911182158029987476766109466552734375,\"plate\":\"#000000\"},\"napa-autopro-dv-auto.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/napa-autopro-dv-auto.webp?v=9ca79aa25b\",\"w\":1931,\"h\":800,\"ink\":0.313000000000000000444089209850062616169452667236328125,\"plate\":\"#000000\"},\"pete-moto-racing-team.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/pete-moto-racing-team.webp?v=e52df4e44d\",\"w\":2000,\"h\":363,\"ink\":0.326000000000000011990408665951690636575222015380859375,\"plate\":\"#000000\"},\"proteksport.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/proteksport.webp?v=b79d09c38e\",\"w\":2000,\"h\":477,\"ink\":0.24299999999999999378275106209912337362766265869140625,\"plate\":\"#000000\"},\"red-bull.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/red-bull.webp?v=06877ad546\",\"w\":1353,\"h\":800,\"ink\":0.291999999999999981792342396147432737052440643310546875,\"plate\":\"#ffffff\"},\"remorkoto.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/remorkoto.webp?v=a2a1e16055\",\"w\":1279,\"h\":800,\"ink\":0.197000000000000008437694987151189707219600677490234375,\"plate\":\"#000000\"},\"remorquage-jrm.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/remorquage-jrm.webp?v=600c43ec58\",\"w\":1387,\"h\":800,\"ink\":0.5180000000000000159872115546022541821002960205078125,\"plate\":\"#000000\"},\"rheaume-canots.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/rheaume-canots.webp?v=08bd9be4ab\",\"w\":2000,\"h\":406,\"ink\":0.24899999999999999911182158029987476766109466552734375,\"plate\":\"#000000\"},\"ride-or-dry.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/ride-or-dry.webp?v=8ea422e4d3\",\"w\":1428,\"h\":800,\"ink\":0.319000000000000005773159728050814010202884674072265625,\"plate\":\"#000000\"},\"sc-bolts.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/sc-bolts.webp?v=f90e6b3f35\",\"w\":1240,\"h\":800,\"ink\":0.198000000000000009325873406851314939558506011962890625,\"plate\":\"#000000\"},\"sport-collette-rive-sud.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/sport-collette-rive-sud.webp?v=2e1585ff7c\",\"w\":1974,\"h\":800,\"ink\":0.66500000000000003552713678800500929355621337890625,\"plate\":\"#000000\"},\"st-moto-sport.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/st-moto-sport.webp?v=e7314517cb\",\"w\":2000,\"h\":513,\"ink\":0.46899999999999997246646898929611779749393463134765625,\"plate\":\"#000000\"},\"steel-pot.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/steel-pot.webp?v=f596426792\",\"w\":2000,\"h\":453,\"ink\":0.2290000000000000091038288019262836314737796783447265625,\"plate\":\"#000000\"},\"x-track.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/x-track.webp?v=d748c49108\",\"w\":2000,\"h\":306,\"ink\":0.55300000000000004707345624410663731396198272705078125,\"plate\":\"#000000\"},\"ycf.webp\":{\"url\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/sponsors\\\/ycf.webp?v=e21dd53994\",\"w\":2000,\"h\":711,\"ink\":0.6109999999999999875655021241982467472553253173828125,\"plate\":\"#000000\"}},\"distances\":[[\"FROM\",\"TO\",\"KM\",\"ROUTE\"],[\"KTR\",\"SHANNONVILLE\",\"458\",\"https:\\\/\\\/www.google.com\\\/maps\\\/dir\\\/?api=1\\u0026origin=46.30579907818198,-72.6905438\\u0026destination=44.224923220973814,-77.1591345\\u0026travelmode=driving\"],[\"SHANNONVILLE\",\"MECAGLISSE\",\"450\",\"https:\\\/\\\/www.google.com\\\/maps\\\/dir\\\/?api=1\\u0026origin=44.224923220973814,-77.1591345\\u0026destination=46.21861015578252,-74.10801541601303\\u0026travelmode=driving\"],[\"MECAGLISSE\",\"RDL\",\"475\",\"https:\\\/\\\/www.google.com\\\/maps\\\/dir\\\/?api=1\\u0026origin=46.21861015578252,-74.10801541601303\\u0026destination=47.825519874764915,-69.54155227116394\\u0026travelmode=driving\"],[\"RDL\",\"MONTMAGNY\",\"134\",\"https:\\\/\\\/www.google.com\\\/maps\\\/dir\\\/?api=1\\u0026origin=47.825519874764915,-69.54155227116394\\u0026destination=46.93829935776403,-70.55348868714374\\u0026travelmode=driving\"],[\"MONTMAGNY\",\"SHAWINIGAN\",\"215\",\"https:\\\/\\\/www.google.com\\\/maps\\\/dir\\\/?api=1\\u0026origin=46.93829935776403,-70.55348868714374\\u0026destination=46.558720786614664,-72.74311380511871\\u0026travelmode=driving\"],[\"SHAWINIGAN\",\"GP3R\",\"42\",\"https:\\\/\\\/www.google.com\\\/maps\\\/dir\\\/?api=1\\u0026origin=46.558720786614664,-72.74311380511871\\u0026destination=46.345324499739895,-72.55590722883606\\u0026travelmode=driving\"],[\"GP3R\",\"MONTMAGNY\",\"197\",\"https:\\\/\\\/www.google.com\\\/maps\\\/dir\\\/?api=1\\u0026origin=46.345324499739895,-72.55590722883606\\u0026destination=46.93829935776403,-70.55348868714374\\u0026travelmode=driving\"],[\"MONTMAGNY\",\"LOMBARDY\",\"565\",\"https:\\\/\\\/www.google.com\\\/maps\\\/dir\\\/?api=1\\u0026origin=46.93829935776403,-70.55348868714374\\u0026destination=44.834189738380125,-76.07022880258425\\u0026travelmode=driving\"],[\"LOMBARDY\",\"KTR\",\"368\",\"https:\\\/\\\/www.google.com\\\/maps\\\/dir\\\/?api=1\\u0026origin=44.834189738380125,-76.07022880258425\\u0026destination=46.30579907818198,-72.6905438\\u0026travelmode=driving\"]],\"promotions\":[[\"SEASON\",\"NAME\",\"DISCIPLINE\",\"FROM\",\"TO\"],[\"2026\",\"MARC DUMONT\",\"SM\",\"AMATEUR\",\"INTER\"],[\"2026\",\"DOMINIC ANDR\\u00c9-MONGEON\",\"SM\",\"AMATEUR\",\"INTER\"],[\"2026\",\"BEN BELLEVILLE\",\"SM\",\"AMATEUR\",\"INTER\"],[\"2026\",\"CHELSEA PARENT-BOURSIER\",\"SM\",\"AMATEUR\",\"INTER\"],[\"2026\",\"ALEXIS MARINEAU\",\"SM\",\"AMATEUR\",\"INTER\"],[\"2026\",\"SHAWN ALEXIS GRONDIN GAUTHIER\",\"SM\",\"AMATEUR\",\"INTER\"],[\"2026\",\"VALERIAN RENAUD\",\"SM\",\"AMATEUR\",\"INTER\"],[\"2026\",\"THOMAS LAROUCHE\",\"SM\",\"INTER\",\"PRO\"],[\"2026\",\"THOMAS MANSEAU\",\"SM\",\"INTER\",\"PRO\"],[\"2026\",\"JEREMIE LAUZI\\u00c8RE\",\"SM\",\"INTER\",\"PRO\"],[\"2026\",\"ANTHONY GOSSELIN\",\"SM\",\"INTER\",\"PRO\"],[\"2026\",\"DANY MARCHAND\",\"SM\",\"INTER\",\"PRO\"],[\"2026\",\"ROMAIN LANGLET\",\"SM\",\"INTER\",\"PRO\"],[\"2026\",\"SYLVAIN FOLLENFANT\",\"SM\",\"INTER\",\"PRO\"],[\"2026\",\"JAGER STOCKILL\",\"SM\",\"INTER\",\"PRO\"],[\"2026\",\"KEVEN LAROCHELLE\",\"SBK\",\"AMATEUR\",\"PRO\"],[\"2026\",\"NATHAN ROY GIROUX\",\"SBK\",\"AMATEUR\",\"PRO\"]],\"mediaKit\":[[\"SEASON\",\"NAME\",\"CODE\"],[\"2026\",\"CHELSEA PARENT-BOURSIER\",\"a1b1e67bff4c9b0ae398bcba38649e1ec0bc1956688ff14fd39e49bb6e17e2bd\"],[\"2026\",\"MAVRICK CYR\",\"4ac4e35292b9f4dac11bb4628bf8a2d44a45e7c11d50e5410db43827190c5234\"],[\"2026\",\"JONATHAN MASSIGNANI\",\"c13ea1910c8ddb0bef760343d1414130744c64cf1b618efddad60f0d53b206f1\"],[\"2026\",\"BENOIT DRAPEAU\",\"1d32bd2af086ad238ee43d2ac8970700cee80d1ea5b1442ca424953b005f2a9a\"]],\"season\":\"2026\",\"galaAccess\":\"public\",\"mediaKitAssets\":{\"script\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/csm-media-kit.js?ver=5e5e2abab3\",\"folder\":\"https:\\\/\\\/canadiansupermoto.com\\\/wp-content\\\/plugins\\\/csm-standings\\\/assets\\\/media-kit\\\/\",\"ver\":\"5e5e2abab3\"}});\n\t})();\n\t<\/script>\n\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":381,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-23883","page","type-page","status-publish","hentry","description-off"],"_links":{"self":[{"href":"https:\/\/canadiansupermoto.com\/en\/wp-json\/wp\/v2\/pages\/23883","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/canadiansupermoto.com\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/canadiansupermoto.com\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/canadiansupermoto.com\/en\/wp-json\/wp\/v2\/users\/381"}],"replies":[{"embeddable":true,"href":"https:\/\/canadiansupermoto.com\/en\/wp-json\/wp\/v2\/comments?post=23883"}],"version-history":[{"count":3,"href":"https:\/\/canadiansupermoto.com\/en\/wp-json\/wp\/v2\/pages\/23883\/revisions"}],"predecessor-version":[{"id":23932,"href":"https:\/\/canadiansupermoto.com\/en\/wp-json\/wp\/v2\/pages\/23883\/revisions\/23932"}],"wp:attachment":[{"href":"https:\/\/canadiansupermoto.com\/en\/wp-json\/wp\/v2\/media?parent=23883"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}