/* ==========================================================================
   FRACTAL FUSION, FTC 27188
   style.css, the only stylesheet on the site.

   ---------------------------------------------------------------------------
   THE PALETTE IS THREE COLOURS
   ---------------------------------------------------------------------------
   --light   periwinkle    a ground, and the text colour on navy
   --dark    slate navy    a ground, and the text colour on periwinkle
   --c1      butter        the accent

   Navy and periwinkle sit at 9.08:1 in both directions, so either one can be
   the ground and the other becomes the text. That is why a section can flip
   from light to dark without a single component changing.

   Butter behaves differently depending on what is behind it:

       on navy          9.70:1    a real text colour, use it for anything
       on periwinkle    1.07:1    invisible, so it can only ever be a shape

   On light grounds butter is a fill, a bar, a rule or an underline, never
   type. That is the one rule the whole palette rests on.

   ---------------------------------------------------------------------------
   TWO TONES, BOTH MIXED FROM currentColor
   ---------------------------------------------------------------------------
   --mute    currentColor at 75%    secondary text, labels, captions
   --hair    currentColor at 18%    borders and rules

   Because they mix from the surrounding text colour, a single token is right
   on either ground and nothing needs an override when a section flips. --mute
   clears AA both ways. Anything fainter is decoration, not reading.

   There is no third tone. If something needs to sit between the ground and
   the text, it is a surface, not a tint. See below.

   ---------------------------------------------------------------------------
   SURFACES INVERT, THEY NEVER TINT
   ---------------------------------------------------------------------------
   A card on a light ground is --dark. A card on a dark ground is --light.
   No half opacity fills, no in between shades. .card--line is the escape
   hatch for when a dense grid would otherwise be a wall of solid slabs.

   Only three components need to know which ground they are on, and they are
   grouped together in one :is() selector: .stat, .btn and .lnk. Everything
   else rides on currentColor and works out for itself.

   ---------------------------------------------------------------------------
   THERE ARE NO LAYOUT BREAKPOINTS
   ---------------------------------------------------------------------------
   Every size is clamp() against the viewport, and the two largest spacing
   steps also read viewport height so short laptops do not lose the fold to
   padding. Layouts collapse on their own using two patterns:

       grids   repeat(auto-fit, minmax(min(100%, X), 1fr))
       rows    flex-wrap, with a flex-basis that decides when an item drops

   The min(100%, X) is what stops a grid track demanding more width than the
   screen has. Without it a narrow phone gets a sideways scrollbar.

   Three @media rules exist. Two are genuine width breakpoints, one in the
   footer and one for phones, each explained where it sits. Mostly they buy
   arrangement rather than size, order and what is on which row, which no clamp
   can express. The phone one also carries a few sizes: the footer's row has to
   hold the brand and five marks at 320px, and shrinking them is only wanted on
   the screens that cannot fit them. As plain clamps those would go on changing
   the wide footer too, which is not the point. The third rule is
   prefers-reduced-motion, a user setting rather than a size.

   ---------------------------------------------------------------------------
   EDITING THIS FILE
   ---------------------------------------------------------------------------
   The palette lives in the :root block directly below. Nothing beneath it
   should ever contain a raw hex value, so the entire site reskins from three
   lines. If you find yourself reaching for a fourth colour, the answer is
   usually a surface that inverts or a tone from currentColor.

   Order: tokens, base, utilities, bands, cards, buttons, links, header,
   footer, section furniture, then one block per page feature.
   ========================================================================== */

:root{

  /* ---- COLOURS, the whole palette ------------------------------------- */
  --light : #DCE7F7;   /* periwinkle. Light ground, text on dark            */
  --dark  : #2E3A55;   /* slate navy. Dark ground, text on light           */
  --c1    : #FBEFA1;   /* butter. SHAPE on light, TEXT on dark             */

  /* ---- TONES, only two ------------------------------------------------
     Both are mixed from currentColor, so they resolve against whatever
     text colour surrounds them. One token works on either ground and
     there is nothing to override when a section flips to navy.          */
  --mute : color-mix(in srgb, currentColor 75%, transparent);  /* AA either way */
  --hair : color-mix(in srgb, currentColor 18%, transparent);  /* borders only  */

  /* ---- FONTS ----------------------------------------------------------- */
  --font  : Archivo, "Helvetica Neue", Arial, sans-serif;
  --mono  : "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* ---- TYPE, fluid ----------------------------------------------------- */
  --t-hero  : clamp(2.25rem, 6.6vw, 4.75rem);
  --t-h1    : clamp(1.85rem, 4.8vw, 3.1rem);
  --t-h2    : clamp(1.35rem, 3.4vw, 2rem);
  --t-h3    : clamp(1.05rem, 1.7vw, 1.25rem);
  --t-body  : clamp(1rem, 1.05vw, 1.0625rem);
  --t-lede  : clamp(1.05rem, 1.6vw, 1.1875rem);
  --t-small : clamp(.9rem, 1vw, .9375rem);
  --t-label : clamp(.66rem, .82vw, .6875rem);
  --t-stat  : clamp(2rem, 4.4vw, 3.2rem);

  /* ---- SPACE, fluid ---------------------------------------------------- */
  --s1 : clamp(.35rem, .6vw,  .5rem);
  --s2 : clamp(.7rem,  1.2vw, 1rem);
  --s3 : clamp(1rem,   2vw,   1.5rem);
  --s4 : clamp(1.4rem, 3vw,   2rem);
  --s5 : clamp(2rem,   4.4vw, 3rem);

  /* These two also lean on viewport height so tall screens breathe and
     short laptops do not waste half the fold on padding. */
  --s6 : clamp(2.5rem, 4vw + 2vh, 4.5rem);
  --s7 : clamp(3rem,   5vw + 3vh, 7rem);

  /* ---- LAYOUT ---------------------------------------------------------- */
  --wrap      : 1140px;
  --wrap-text : 68ch;
  --radius    : 4px;    /* controls: inputs, select, filter buttons     */
  --radius-lg : 1rem;   /* cards and tiles, matched to the button's     */
                        /* two live corners, which are also 1rem        */
  /* The mark is square, so this is its height too, and the bar is only ever as
     tall as the mark plus its padding and the butter rule. Deriving one from
     the other means the height #header reserves before the fetch lands is
     always the height the bar turns out to be, so the page never shifts. */
  --logo-h    : clamp(48px, 5.2vw, 64px);
  --header-h  : calc(var(--logo-h) + 2 * var(--s1) + 2px);
}


/* ==========================================================================
   BASE
   ========================================================================== */

*,*::before,*::after{ box-sizing:border-box }

/* Overscroll. Rubber banding drags the whole document past its own end, and
   what shows in the gap is the canvas, not the body. With no background here
   the body's periwinkle propagates to the canvas, so bouncing at the top left
   a light strip above the navy bar and bouncing at the bottom left one below
   the navy footer. Painting the canvas navy makes both seams invisible, since
   navy is what sits at each end of every page. The body keeps its own light
   background, so nothing inside the page changes.

   Colouring it rather than killing the bounce with overscroll-behavior:none,
   which would also take pull to refresh with it. */
html{
  background:var(--dark);
  -webkit-text-size-adjust:100%;
  scroll-padding-top:calc(var(--header-h) + var(--s2));
}

body{
  margin:0;
  background:var(--light);
  color:var(--dark);
  font-family:var(--font);
  font-size:var(--t-body);
  line-height:1.62;
  -webkit-font-smoothing:antialiased;

  /* Column layout so the footer can be held on the bottom edge. dvh, not vh,
     or mobile browser chrome pushes the footer below the fold on load. */
  min-height:100vh;
  min-height:100dvh;
  display:flex;
  flex-direction:column;
}

/* The footer on the bottom, always. Short pages, which is every placeholder
   page right now, otherwise ended the footer 500px up the screen with bare
   body colour under it. main absorbs the slack, and the section that ends the
   page stretches into it, so the last band carries its own ground all the way
   down rather than handing the last 500px to whatever body happens to be. */
main{ flex:1 0 auto; display:flex; flex-direction:column }
main > :last-child{ flex:1 0 auto }

img{ max-width:100%; height:auto; display:block }

h1,h2,h3,h4{ margin:0; font-weight:800; line-height:1.08; text-wrap:balance }
h1{ font-size:var(--t-h1); font-stretch:120%; text-transform:uppercase; letter-spacing:-.005em }
h2{ font-size:var(--t-h2); font-stretch:118%; text-transform:uppercase; letter-spacing:-.005em }
h3{ font-size:var(--t-h3); font-stretch:105%; font-weight:700; line-height:1.25 }
h4{ font-size:var(--t-body); font-stretch:105%; font-weight:700 }

p{ margin:0 0 var(--s2) }

/* Italic leans into the space behind it, so <em>FIRST</em> reads as joined to
   the word after it however many spaces the markup has. A hair of margin gives
   that space back, in em so it scales with whatever type it lands in. The
   wordmark's Fusion is an em set upright, so it opts out. */
em{ margin-right:.12em }
.brand em{ margin-right:0 }
p:last-child{ margin-bottom:0 }

a{ color:inherit }

::selection{ background:var(--c1); color:var(--dark) }

:focus-visible{ outline:2px solid currentColor; outline-offset:3px; border-radius:2px }

/* Phones paint a translucent grey box over whatever is tapped, in a colour
   that belongs to the browser rather than to this site. It is dropped here and
   replaced by the :active states that sit beside each hover rule below, so a
   tap still confirms itself, just in the site's own language. */
html{ -webkit-tap-highlight-color:transparent }


/* ==========================================================================
   UTILITIES
   ========================================================================== */

/* The one exception to Archivo. */
.mono{ font-family:var(--mono) }

.wrap{
  width:100%;
  max-width:var(--wrap);
  margin-inline:auto;
  padding-inline:clamp(1rem, 4vw, 2rem);
}
.measure{ max-width:var(--wrap-text) }

.label{
  display:block;
  font-family:var(--mono);
  font-size:var(--t-label);
  font-weight:600;
  letter-spacing:.15em;
  text-transform:uppercase;
  color:var(--mute);
  line-height:1.5;
}

.lede{ font-size:var(--t-lede); color:var(--mute); line-height:1.5 }

.stat{
  display:block;        /* it is a <time> in the hero, which is inline by default */
  font-size:var(--t-stat);
  font-weight:800;
  font-stretch:118%;
  line-height:1;
  letter-spacing:-.025em;
  font-variant-numeric:tabular-nums;
}

.rule{ height:8px; width:clamp(72px, 9vw, 112px); background:var(--c1); border-radius:1px; margin-top:var(--s2) }


/* ==========================================================================
   BANDS. Every section is either light or dark. Nothing in between.
   ========================================================================== */

.band{ padding-block:var(--s6) }
.band--light{ background:var(--light); color:var(--dark) }
.band--dark { background:var(--dark);  color:var(--light) }
.band--tight{ padding-block:var(--s5) }

/* Dark context. Only three things need to know which ground they are on,
   because everything else rides on currentColor. */
:is(.band--dark,.card--dark) .stat{ color:var(--c1) }

/* Butter arrives on hover as type on navy (9.70:1) and as a fill on
   periwinkle (1.07:1, never type). Same gesture, legal on either ground. */
:is(.band--dark,.card--dark) .btn{
  --btn-hover-bg : transparent;
  --btn-hover-fg : var(--c1);
}


/* ==========================================================================
   CARDS. A card inverts its ground. Never a tint, never a partial fill.
   ========================================================================== */

.card{
  border-radius:var(--radius-lg);
  padding:var(--s3);
  display:block;
  text-decoration:none;
}
.card--dark { background:var(--dark);  color:var(--light) }
.card--light{ background:var(--light); color:var(--dark) }

/* Outlined rather than filled, for rows where solid cards would be too heavy. */
.card--line{ background:transparent; border:1px solid var(--hair) }

a.card{ transition:transform .16s ease }
a.card:hover{ transform:translateY(-2px) }
a.card:active{ transform:translateY(0) }


/* ==========================================================================
   BUTTONS. One shape. A hairline in the button's own text colour, notched
   at the top-left and bottom-right so the stroke breaks open there and the
   other two corners round. Outline is the default. --accent is the fill,
   for the one primary action on a page.
   ========================================================================== */

.btn{
  --notch        : 1.25rem;        /* how far back the two open corners cut */
  --btn-bg       : transparent;
  --btn-hover-bg : var(--c1);
  --btn-hover-fg : var(--dark);

  display:inline-block;
  font-family:var(--font);
  font-size:var(--t-small);
  font-weight:700;
  font-stretch:110%;
  line-height:1;
  padding:.875rem 1.5rem;
  color:inherit;
  background:var(--btn-bg);
  border: 1.5px solid currentColor;
  border-radius:0 1rem 0 1rem;     /* only the two live corners ever show it */
  clip-path:polygon(
    var(--notch) 0,
    100% 0,
    100% calc(100% - var(--notch)),
    calc(100% - var(--notch)) 100%,
    0 100%,
    0 var(--notch)
  );
  text-decoration:none;
  cursor:pointer;
  transition:color .25s, background-color .25s;
}

.btn:hover,
.btn:active{ background:var(--btn-hover-bg); color:var(--btn-hover-fg) }
.btn:active{ transform:translateY(1px) }

/* clip-path cuts away the global focus ring, offset and all. Draw it inside. */
.btn:focus-visible{ outline:none; box-shadow:inset 0 0 0 2px currentColor }

.btn--accent{ --btn-bg:var(--c1); --btn-hover-bg:var(--light); color:var(--dark) }


/* ==========================================================================
   LINKS. Navy text with a yellow bar that fills upward on hover.
   ========================================================================== */

.lnk{
  color:inherit;
  font-weight:600;
  text-decoration:none;
  padding:0 2px;
  background-image:linear-gradient(var(--c1),var(--c1));
  background-repeat:no-repeat;
  background-size:100% 6px;
  background-position:0 92%;
  transition:background-size .18s ease;
}
.lnk:hover,.lnk:active,.lnk:focus-visible{ background-size:100% 100% }

/* On navy the yellow is legible type, so it simply becomes the link colour. */
:is(.band--dark,.card--dark) .lnk{
  color:var(--c1);
  background-image:none;
  text-decoration:underline;
  text-decoration-thickness:2px;
  text-underline-offset:3px;
}

/* A light card sitting inside a dark band is a light ground in its own right,
   so it takes the light treatment back. The dark rules above match on an
   ancestor, and the nearest surface is what should decide. */
.card--light .lnk{
  color:inherit;
  background-image:linear-gradient(var(--c1),var(--c1));
  background-repeat:no-repeat;
  background-size:100% 6px;
  background-position:0 92%;
  text-decoration:none;
}
.card--light .lnk:hover,
.card--light .lnk:active,
.card--light .lnk:focus-visible{ background-size:100% 100% }
.card--light .stat{ color:inherit }

/* A highlighter. Every value is in em, so the same class works on a 16px
   paragraph and on a 76px hero line: at fixed pixels the padding and corner
   shrink to a sticker once the type is large. Weight is inherited for the same
   reason, since a fixed 600 would thin any heading it landed in, and the fill
   is the emphasis anyway. box-decoration-break keeps the padding and both
   corners intact on each fragment when a highlight breaks across a line. */
.mark{
  background:var(--c1);
  color:var(--dark);
  font-weight:inherit;
  padding:.04em .2em;
  border-radius:.08em;
  -webkit-box-decoration-break:clone;
          box-decoration-break:clone;
}


/* ==========================================================================
   SITE HEADER
   The nav carries a flex-basis, so it drops to its own row on its own when
   there is no longer room beside the wordmark. Below 37rem that decision is
   taken by the phone query at the foot of the file instead, because the
   reserved height has to know which way it went.
   ========================================================================== */

/* The bar is pinned by its wrapper, not by .site-head itself. A sticky element
   only sticks while its own containing block is on screen, and .site-head's
   containing block is this one-bar-tall div, so sticking it there let the bar
   scroll straight off once you passed its own height. Sticking the wrapper
   instead gives it body as its containing block: pinned for the whole scroll.
   Sticky also keeps the bar in flow, so it reserves its own height: no offset
   to keep in sync, and no jump when the nav wraps to a second line. */
#header{
  position:sticky; top:0; z-index:50;
  min-height:var(--header-h);           /* reserve space before the fetch lands */
}

/* Opaque, so the page genuinely disappears underneath rather than showing
   through. Every ground this bar can sit over is one of the two palette
   colours, so a flat navy fill is all it needs. */
.site-head{
  background:var(--dark); color:var(--light);
  min-height:var(--header-h);
  display:flex; align-items:center;
  padding-block:var(--s1);
  /* A butter hairline closes the bar off. Navy over a light page separates
     on its own, but over a navy section the two grounds merge, and this rule
     is the only thing that draws the edge there. Butter reads on navy either
     way, so one line covers both cases. */
  border-bottom:2px solid var(--c1);
}
.site-head .wrap{
  display:flex; align-items:center; flex-wrap:wrap;
  gap:var(--s1) var(--s3);   /* the row gap only bites once the nav wraps */
}

.brand{ display:flex; align-items:center; gap:.625rem; text-decoration:none; color:var(--light); flex:0 0 auto }
.brand img{ width:clamp(32px, 3.4vw, 40px); height:auto; transition:transform .3s ease }
.brand:hover img{ transform:rotate(12deg) }

/* The bar's mark is the larger of the two. --header-h derives from this value,
   so the bar and the space reserved for it cannot drift. */
.site-head .brand img{ width:var(--logo-h) }
.brand span{
  font-weight:800; font-stretch:120%; font-size:clamp(.9rem, 1.1vw, 1rem);
  text-transform:uppercase; letter-spacing:.01em; line-height:1.05;
}
.brand em{ font-style:normal; color:var(--c1) }

/* Beside a mark that size the shared wordmark size reads as an afterthought,
   so the header's scales off the mark instead, at the same mark-to-wordmark
   ratio the footer sets. One lockup, two sizes, no second number to keep. */
.site-head .brand span{ font-size:calc(var(--logo-h) / 2.5) }

/* Both halves ease rather than snap, on the same .3s as the mark's tilt above,
   so the whole gesture moves as one thing. The reduced-motion rule at the foot
   of the file switches all three off together. */
.brand span,
.brand em{ transition:color .3s ease }

/* The two colours trade places on hover: Fractal takes the butter, Fusion
   takes the ground colour. Stated on both halves rather than letting Fractal
   ride an inherited link hover, so the swap survives a change to that rule.
   One anchor holds mark and wordmark, so hovering either one runs the lot. */
.brand:hover span{ color:var(--c1) }
.brand:hover em  { color:var(--light) }

.site-nav{
  display:flex; gap:var(--s3);
  overscroll-behavior-x:contain;   /* swiping past the last tag stays in the row */
  flex:1 1 22rem;          /* below ~22rem of free space the nav wraps to its own line */
  min-width:0;             /* lets the nav shrink and scroll rather than stretch the bar */
  overflow-x:auto;
  scrollbar-width:none;
}

/* Right aligns the nav when there is spare room, and collapses to zero when
   there is not. justify-content:flex-end would instead push the overflow off
   the left edge, where it cannot be scrolled back into view. */
.site-nav > a:first-child{ margin-left:auto }
.site-nav::-webkit-scrollbar{ display:none }
.site-nav a{
  font-size:var(--t-small); font-weight:600;
  color:var(--mute); text-decoration:none;
  white-space:nowrap; padding-block:.375rem;
  border-bottom:3px solid transparent;
}
.site-nav a:hover,
.site-nav a:active{ color:var(--light) }
.site-nav a.is-active{ color:var(--light); border-bottom-color:var(--c1) }
/* ==========================================================================
   SITE FOOTER
   One row that wraps: brand, legal and contact strip, marks. No divider and
   no second row, so the whole thing is one line tall wherever it fits.
   ========================================================================== */

.site-foot{ background:var(--dark); color:var(--light); padding-block:var(--s3) }
.site-foot a{ color:var(--mute); text-decoration:none }
.site-foot a:hover,
.site-foot a:active{ color:var(--c1) }

/* Brand left, marks right, strip between. space-between does that on one line
   and stacks the three in source order when the line runs out. */
.site-foot .wrap{
  display:flex; flex-wrap:wrap; align-items:center;
  justify-content:space-between;
  gap:var(--s2) var(--s3);
}

/* Held on one line as far down as the type can legibly go. The font size is
   the one place on the page that does not sit on a --t token: this strip has
   to carry three fixed strings side by side, so it keys off the viewport
   instead, bottoming out at the floor for legible mono. Once it has a line to
   itself it centres, which is also where it starts wrapping internally. */
.foot-legal{
  /* Grows into whatever the brand and the marks leave, so on one line it sits
     centred between them. nowrap here on purpose: inline, this strip is either
     one line or it is not inline at all. It never stacks between the brand and
     the marks, which is what the breakpoint below is for. */
  flex:1 1 auto;
  display:flex; flex-wrap:nowrap; justify-content:center;
  gap:var(--s1) var(--s3);
  font-family:var(--mono); font-size:clamp(.58rem, 1.1vw, .6875rem);
  letter-spacing:.09em; text-transform:uppercase; color:var(--mute);
}
.foot-legal > *{ white-space:nowrap }

/* Brand marks, not words. Every link is a --hit box, finger sized, holding a
   quiet --mark glyph. Each one paints its mark as a mask filled with
   currentColor: the shape lives in files/logos, the colour stays in the
   palette, so they inherit the mute-to-butter hover set above and nothing is
   baked into the asset. */
.foot-social{
  --mark : 1.375rem;              /* the glyph */
  --hit  : 2.75rem;               /* the tap target around it */

  display:flex; flex-wrap:wrap; align-items:center;
  gap:var(--s1);
  min-width:0;                    /* lets the row wrap instead of running past the wrap */
}
.foot-social .soc{
  display:inline-flex; align-items:center; justify-content:center;
  width:var(--hit); min-height:var(--hit);
}
.foot-social .soc::before{
  content:"";
  width:var(--mark); height:var(--mark);
  background:currentColor;
  -webkit-mask:var(--soc) center / contain no-repeat;
          mask:var(--soc) center / contain no-repeat;
}

/* One line per mark. Paths are relative to this stylesheet, which sits beside
   the folder, so they hold on every page. */
.soc--instagram{ --soc:url("files/logos/instagramLogo.svg") }
.soc--tiktok   { --soc:url("files/logos/tiktokLogo.svg") }
.soc--youtube  { --soc:url("files/logos/youtubeLogo.svg") }
.soc--ftcscout { --soc:url("files/logos/ftcscoutLogo.svg") }
.soc--email    { --soc:url("files/logos/emailIcon.svg") }

/* The binoculars are drawn tall and narrow, so at a shared height they read
   lighter than the marks beside them. Widening the box is the wrong lever,
   that would break the rhythm, so the glyph itself grows instead. */
.soc--ftcscout::before{ --mark:1.625rem }

/* Pull back the first and last boxes' slack so the marks, not the empty half
   of their targets, line up with the wrap edges. */
.foot-social .soc:first-child{ margin-left:calc((var(--mark) - var(--hit)) / 2) }
.foot-social .soc:last-child{ margin-right:calc((var(--mark) - var(--hit)) / 2) }

/* The one breakpoint in the stylesheet, and it buys something no fluid value
   can: order. Wide, the strip sits between the brand and the marks on one
   line. Below this it drops to a line of its own at the bottom and the marks
   come up beside the brand, which is a different order, not a different size.
   It is set a little above the width where the strip's single line actually
   runs out, so the strip is never caught stacking in the middle of the row. */
@media (max-width:58rem){
  .site-foot .foot-social{
    order:1;
    margin-left:auto;      /* holds the marks right even alone on their line */
  }
  .site-foot .foot-legal{
    order:2;
    flex-basis:100%;       /* its own line, so its centring has room to read */
    flex-wrap:wrap;        /* and now it may break, since the line is its own */
  }
}


/* ==========================================================================
   PAGE HEADER, the block under the nav on every interior page
   ========================================================================== */

/* Light, on the same ground as the band below it, so the two run together as
   one opening rather than reading as a slab and then the page. That is also
   why the bottom padding is shorter than the top: with no colour change to
   justify it, a full --s6 either side left the title stranded from its own
   content. The hairline carries the edge the tone change used to. */
.page-head{
  background:var(--light); color:var(--dark);
  padding-block:var(--s5) var(--s4);
  border-bottom:1px solid var(--hair);
}
.page-head h1{ margin-top:var(--s2); max-width:16ch }
.page-head .lede{ margin-top:var(--s3); max-width:52ch }


/* ==========================================================================
   HOME hero
   ========================================================================== */

/* Navy, and the ground is declared in the markup with band--dark rather than
   set here, because three rules downstream key off that class: the stats turn
   butter, the buttons hover to butter type instead of a butter fill, and links
   become butter. Setting background here instead would leave all three reading
   the hero as a light section. No rule along the bottom either: the strip below
   is periwinkle, so the tone change is the edge.

   Every gap in here is one step down the space scale from where it started. */
.hero{
  padding-block:var(--s3) var(--s2);
  overflow:hidden;      /* contains the robot's full bleed row, see below */
}

/* With the robot in place the band is edged by a butter rule at each end: the
   header's at the top and the stage's own at the bottom. A pad outside either
   of them reads as a navy gutter beside a border, so both go. With no robot
   there are no rules and the pads are what keep the words off the header and
   the band below, so they only go when the stage has something in it. */
.hero:has(.hero-stage:not(:empty)){ padding-block:0 }
/* One cell, holding the words and the robot on top of each other. The words
   carry the buttons now, so everything the hero says is inside the robot's row
   and above the butter rule that closes it. Same arrangement at every width: a
   phone gets the band a desktop does, only narrower. */
.hero .wrap{
  display:grid;
  grid-template-columns:minmax(0, 1fr);
}
/* The pad is clearance between the buttons and the butter rule. It only shows
   when the words are what make the row tall, which is the narrow case: where
   the robot is taller, the head is centred in the row and the pad disappears
   into the centring. */
.hero .wrap > .hero-head{
  grid-area:1 / 1; align-self:center; min-width:0;
  /* The pad is on the words, not on the hero, so the band still starts flush
     under the header while the label keeps a little air beneath the rule. A
     hero level pad would push the band down with it and put the gap back.

     The bottom is a step below the top rather than a step above it. The buttons
     already carry their own bracket below the type, so the pad only has to keep
     that shape off the butter rule, not clear the type itself. */
  padding-block:var(--s2);
}

.hero h1{ font-size:var(--t-hero); max-width:15ch; margin-top:var(--s1) }
.hero .lede{ margin-top:var(--s2); max-width:46ch }

/* z-index over the stage's 1, or the viewer's canvas covers the buttons and
   swallows the clicks. The margin is the clearance the row gap used to give
   them, now that they are inside the head rather than a row below it. */
.hero-actions{
  position:relative; z-index:2;
  margin-top:var(--s3);
  display:flex; flex-wrap:wrap; gap:var(--s2);
}

/* Row one, over the words, and the full width of the window rather than of the
   wrap: the robot is meant to be able to cross the whole screen once it is
   zoomed in. The margin is the usual full bleed pair to width:100vw, and
   .hero's clip absorbs the scrollbar's worth that 100vw overshoots by.

   min-height, because otherwise the row is only as tall as the words and the
   robot would be tiny. It is the robot that decides how tall row one is, and
   the buttons sit under whatever that comes to. */
.hero-stage{
  --stage-h:clamp(20rem, 26vw + 14vh, 38rem);

  grid-area:1 / 1;
  z-index:1;
  position:relative;
  overflow:hidden;
  width:100vw;
  margin-inline:calc((100% - 100vw) / 2);

  /* No climb needed any more: the hero drops its own top pad whenever the stage
     is filled, so row one already starts flush under the header. */
  min-height:var(--stage-h);

  /* The same butter rule the header carries, for the same reason: it is the
     edge of a band. Without it a zoomed in robot ends on a raw cut, which reads
     as clipping rather than as a deliberate edge. Two ends of one band, the
     header's rule at the top and this at the bottom. */
  border-bottom:2px solid var(--c1);
}

/* Transparent so the hero ground shows through, and never a scroll trap on
   touch: the viewer only takes the pointer when a drag starts on it.

   The canvas is wider than the window on purpose. A model-viewer centres its
   model in its own box, so moving the box is how the robot gets placed, and
   .hero-stage clips the overhang. Sliding the camera's look at point sideways
   would do it too, but it makes the robot swing across the screen as it turns
   rather than spin on the spot: 97px of drift measured, against 0px for this.

   The slide is a proportion of the window, not a distance, so the robot sits in
   the same place relative to the screen at every size rather than drifting as
   the window changes. 30vw right of centre puts its middle at 80% across, which
   is where it sits at 1024px and wider.

   Below that it goes further right, on the same ramp the lift above uses and
   from the same 64rem start, so both corrections come in together as the window
   narrows. The words are what it is getting out of the way of: the heading wraps
   to more lines as the band narrows, so the empty right hand end it was sitting
   over shrinks at exactly the width the robot would otherwise start crossing the
   text. The ramp is capped at 10vw so the robot cannot walk off the edge on the
   smallest screens; at that cap its middle is 90% across.

   The lift is in the insets above, not the transform, and it is zero on
   anything 1024px or wider. Below that the band is nearly as tall as the words
   while the robot inside it has been walked back by the camera, so a centred
   robot leaves a hole above it and sits on the lede underneath. Raising it
   fills the hole, and it uncovers the lede on the way: the space it moves into
   is the short right hand end of the first heading lines, which is empty.

   Growing the canvas rather than moving it makes it taller than the band, and a
   model-viewer sizes its model against the frame height, so the robot would
   come out bigger by the same ratio. main.js divides that ratio back out of the
   resting distance, which is why restingRadius takes the element.

   It used to be guarded against running off a narrow screen. It is not any
   more and does not need to be: what made a phone's robot too big to sit at
   80% was the camera holding one fixed distance, and main.js now walks that
   distance back as the window narrows. A little of the robot may cross the
   right edge on the smallest screens, which is intended. The band is a full
   bleed crop at both ends anyway.

   Zoomed in, the robot grows about wherever that has put it and crosses the
   words on its way to filling the window. */
.hero-stage model-viewer{
  position:absolute;
  left:-35%;
  width:170%;

  /* Anchored to the band's bottom and grown upward, rather than moved upward.
     Twice the lift added above and the bottom pinned to 0 puts the canvas
     middle one lift higher while its bottom edge stays on the butter rule, so a
     zoomed in robot still reaches the bottom of the band. Translating it
     instead, which is what this used to do, carried the bottom edge up with it
     and left a strip of bare navy behind the buttons. */
  top:calc(-2 * max(0px, (64rem - 100vw) * 0.12));
  bottom:0;
  height:auto;   /* model-viewer ships a default height that would beat the insets */

  transform:translateX(calc(30vw + min(10vw, max(0px, (64rem - 100vw) * 0.09))));
  background-color:transparent;
  --poster-color:transparent;
  touch-action:pan-y;
}

/* The viewer ships a loading bar in its shadow root, a translucent black rule
   pinned across the top of the stage. It is not in the palette and there is
   nothing here worth waiting for, so it goes. ::part is the only way in. */
.hero-stage model-viewer::part(default-progress-bar){ display:none }

/* No model, no empty box. main.js sets this when it declines to fetch, so the
   hero collapses back to exactly the layout it had before the robot existed. */
.hero-stage:empty{ display:none }



/* The three numbers are very different widths: a countdown like 17:22:11:40 is
   about 6.7em where 2,000+ is 3.4em. Equal columns cannot hold both at one
   size, so the cells are sized by their contents and share the leftover space
   equally. Each number gets exactly the width it needs and the visual gaps
   stay even, because every cell takes the same extra.

   The row never wraps. It holds all three side by side down to a 320px phone,
   which is what --t-stat's lower bound and the short labels are sized for. */
.statrow{
  display:flex; flex-wrap:wrap;
  gap:var(--s3) var(--s4);
}

/* The strip is the numbers and nothing else, so it does not need a band's full
   padding to hold them. */
.stats{ padding-block:var(--s3) }
.statrow > div{ flex:1 1 auto }

/* Its own size is the point, so the only thing it needs is a promise not to
   fold in the middle of a clock. */
.stat--timer{ white-space:nowrap }
.statrow .label{ margin-top:var(--s1) }


/* The home page strip holds its three numbers on one line at every width,
   down to a 320px phone. Three things make that fit, and all three are
   needed: cells sized by their contents rather than in equal thirds, a gap
   that gives back its space as the screen narrows, and a stat size whose
   lower bound is small enough that the countdown, much the widest of the
   three, still clears its neighbours.

   The labels are one or two short words for the same reason. Lengthen one
   and it becomes the widest thing in its cell, and the row stops fitting. */
.stats .statrow{
  flex-wrap:nowrap;
  /* Wide enough on a laptop that the three read as separate figures, and
     wound back on a phone to whatever is left once the numbers have taken
     their width. 4.2vw is the most a 320px screen can give and still hold
     the row on one line. */
  gap:clamp(0.85rem, 4.2vw, 3.25rem);
  align-items:start;
  justify-content:center;
}
/* Centred within its own cell too, so each number sits over its label rather
   than the pair sharing a left edge. */
.stats .statrow > div{ flex:0 1 auto; min-width:0; text-align:center }
.stats .statrow .label{ white-space:nowrap }
.stats .stat{ font-size:clamp(1.05rem, 4.6vw, 3.2rem) }


/* ==========================================================================
   GRIDS
   ========================================================================== */

.grid{ display:grid; gap:var(--s3) }
.grid--3{ grid-template-columns:repeat(auto-fit, minmax(min(100%, 16rem), 1fr)) }
.grid--2{ grid-template-columns:repeat(auto-fit, minmax(min(100%, 20rem), 1fr)) }


/* ==========================================================================
   ENGINEERING LOG
   ========================================================================== */

.log-bar{
  display:flex; flex-wrap:wrap; align-items:baseline;
  gap:var(--s2) var(--s3);
  margin-bottom:var(--s4);
}

.log-filters{ display:flex; flex-wrap:wrap; gap:var(--s1) var(--s2) }
.log-filters button{
  font-family:var(--font);
  font-size:var(--t-small);
  font-weight:600;
  line-height:1;
  padding:.55rem .9rem;
  border:1px solid var(--hair);
  border-radius:var(--radius);
  background:transparent;
  color:inherit;
  cursor:pointer;
  transition:background-color .14s ease, color .14s ease;
}
.log-filters button:hover,
.log-filters button:active{ border-color:currentColor }
.log-filters button:active{ transform:translateY(1px) }
/* The selected filter inverts its ground, the same way a card does. */
.log-filters button.is-active{ background:var(--dark); color:var(--light); border-color:var(--dark) }

.log-count{ margin-left:auto }

.log-grid{
  display:grid;
  gap:var(--s3);
  grid-template-columns:repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
}

.log-card{ padding:0; overflow:hidden; display:flex; flex-direction:column }
.log-thumb{ width:100%; aspect-ratio:8 / 5; object-fit:cover }
.log-card .log-meta{ padding:var(--s3) var(--s3) 0 }
.log-card h3{ padding:var(--s2) var(--s3) 0 }
/* Grid rows are equal height, so the summary takes up any spare room. That
   keeps every card's footer on its bottom edge, thumbnail or not. */
.log-summary{ padding:var(--s2) var(--s3) var(--s3); color:var(--mute);
  font-size:var(--t-small); flex:1 1 auto }
.log-card .sample{ margin:0 var(--s3) var(--s3) }

/* Marks the sample entries that shipped with the site. Deliberately hard to
   miss, so none of them reach the public site as though they were real. */
.sample{
  display:inline-block;
  font-family:var(--mono);
  font-size:var(--t-label);
  font-weight:600;
  letter-spacing:.1em;
  text-transform:uppercase;
  background:var(--c1);
  color:var(--dark);
  padding:.3rem .55rem;
  border-radius:2px;
}
.page-head .sample{ margin-top:var(--s3) }


/* ---- single entry ---- */

.entry-body{ font-size:var(--t-lede); line-height:1.68 }
.entry-body p{ margin-bottom:var(--s3) }

.entry-figure{ margin:var(--s5) 0 0; max-width:var(--wrap-text) }
.entry-figure img{ width:100%; border-radius:var(--radius) }
.entry-figure figcaption{ margin-top:var(--s2) }

.entry-nav{
  display:flex; flex-wrap:wrap; gap:var(--s3);
  justify-content:space-between;
  margin-top:var(--s6); padding-top:var(--s3);
  border-top:1px solid var(--hair);
}
.entry-step{
  display:grid; gap:.35rem;
  text-decoration:none; color:inherit;
  max-width:26rem; font-weight:600;
}
.entry-step:hover span:last-child,
.entry-step:active span:last-child{ text-decoration:underline; text-underline-offset:3px }
.entry-step--next{ text-align:right; margin-left:auto }


/* ==========================================================================
   SPONSOR STRIP
   One set of logos sits in the markup. main.js clones it until half the run
   overflows the viewport, then the run slides exactly half its width, which
   lands a clone where the original was and loops with no seam.

   The trailing gap is padding on each set rather than a gap on the run: with
   gap, N sets have only N-1 gaps, the two halves are not the same width, and
   the loop jumps by half a gap on every cycle.
   ========================================================================== */

.marquee{
  --spon-gap : clamp(1.5rem, 4vw, 3rem);
  margin-top:var(--s2);
  overflow:hidden;

  /* Room inside the clip box for a name to slide down into. overflow cannot be
     hidden on one axis and visible on the other, so without this the reveal is
     cut off at the logo line. */
  padding-bottom:1.25rem;

  /* Logos enter and leave instead of being sliced off at the edge. Black here
     is an alpha stop, not a colour: the mask only reads the alpha channel. */
  -webkit-mask-image:linear-gradient(90deg, transparent, black 7%, black 93%, transparent);
          mask-image:linear-gradient(90deg, transparent, black 7%, black 93%, transparent);
}

/* Its own paddings rather than band--tight's: the label wants air above it,
   the logos need very little under them, and --s5 on both sides was the gap
   you could see. */
/* The strip is a label and a line of logos, so it wants very little of its
   own. Nothing at the bottom: the marquee already carries the clip room the
   hover reveal needs, and a band pad under that reads as a second gap.

   The band after it gives up most of its top pad too. --s6 is written for two
   blocks of text meeting; against a row of logos it left a hole. */
.sponsors{ padding-block:var(--s2) 0 }
.sponsors + .band{ padding-top:var(--s3) }

.marquee-run{ display:flex; width:max-content }
.marquee-run.is-running{ animation:marquee var(--marquee-time, 40s) linear infinite }
.marquee:is(:hover,:focus-within) .marquee-run{ animation-play-state:paused }

@keyframes marquee{ to{ transform:translateX(-50%) } }

.marquee-set{
  display:flex; align-items:center;
  gap:var(--spon-gap);
  padding:0 var(--spon-gap) 0 0;   /* the trailing gap. See the note above. */
  margin:0; list-style:none;
}

.marquee-set > li{ display:flex }

/* Each cell is the sponsor's link. Height is a variable because the slide
   distance below is measured from it. */
.spon{
  --spon-h : clamp(56px, 6vw, 80px);
  position:relative; flex:0 0 auto;
  display:flex; align-items:center; justify-content:center;
  width:clamp(104px, 11vw, 140px);
  height:var(--spon-h);
  text-decoration:none;
}

/* The name starts dead centre, where the mark covers it, and slides down out
   from under it. It stays at z-index 0 for the whole trip, so the mark occludes
   it on the way out and it reads as coming from behind rather than fading in
   on top. */
.spon-name{
  position:absolute; left:50%; top:50%; translate:-50% -50%;
  z-index:0;
  white-space:nowrap;
  font-family:var(--mono); font-size:var(--t-label); font-weight:600;
  letter-spacing:.14em; text-transform:uppercase;
  color:var(--mute);
  opacity:0;
  transition:translate .32s ease, opacity .2s ease;
}
.spon img{
  position:relative; z-index:1;
  max-width:100%; max-height:100%; width:auto;
  object-fit:contain;
}

/* Keyboard and touch get the same reveal a pointer does, and the strip holds
   still for it. */
:is(.spon:hover, .spon:active, .spon:focus-visible) .spon-name{
  opacity:1;
  translate:-50% calc(-50% + var(--spon-h) / 2 + .5rem);
}


/* ==========================================================================
   SECTION FURNITURE
   ========================================================================== */

.section-bar{
  display:flex; flex-wrap:wrap; align-items:end;
  gap:var(--s2) var(--s4);
  justify-content:space-between;
  margin-bottom:var(--s4);
}
/* Its own step below --t-h2. The heading shares a row with the button, so on a
   narrow window it is working in whatever width the button leaves rather than
   the whole wrap, and --t-h2's floor was tall enough there to break it into
   four lines. Same slope as --t-h2, so from about 640px up this is the size it
   always was; only the floor moves. .9rem is the largest floor that still fits
   the heading on two lines in the 128px an SE leaves it: 1rem is four. */
.section-bar h2{ font-size:clamp(.9rem, 3.4vw, 2rem); margin-top:var(--s1) }

/* The heading takes the row and shrinks into it, so it and the button stay on
   one line. Without a basis the heading asks for its full width up front and
   the button is pushed onto a line of its own long before the row is actually
   full. The basis is small on purpose: it is only the width at which the pair
   gives up and stacks, and the heading grows past it to fill whatever the
   button leaves. At 5rem that is about a 330px window, so every phone in use
   keeps the two side by side. */
.section-bar > :first-child{ flex:1 1 5rem; min-width:0 }

/* Section furniture, not a page action: it sits beside a heading rather than
   under a paragraph, so it is cut down from the standard button. The notch
   comes back with it, or the corner cut eats most of a shorter button. */
.section-bar .btn{
  --notch:.9rem;
  padding:.6rem 1.05rem;
}

/* Two columns where there is room, stacked where there is not. */
.split{
  display:grid; gap:var(--s5) var(--s4);
  grid-template-columns:repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
  align-items:start;
}
.split :is(h1,h2){ margin-bottom:var(--s3) }

.cta-pair{
  display:grid; gap:var(--s5) var(--s4);
  grid-template-columns:repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
}
.cta-pair h2{ margin:var(--s1) 0 var(--s2) }
.cta-pair .lede{ margin-bottom:var(--s3) }

.note-card h3{ margin:var(--s1) 0 var(--s2) }
.note-card p + p{ margin-top:var(--s2) }

.support-grid{ margin-top:var(--s4) }
.support-grid h3{ margin:var(--s1) 0 var(--s2) }

.cost-list{ margin:0 0 var(--s3); padding:0; list-style:none }
.cost-list li{
  padding:var(--s2) 0;
  border-top:1px solid var(--hair);
}
.cost-list li:first-child{ border-top:0 }

.note-line{ font-size:var(--t-label); letter-spacing:.08em; text-transform:uppercase; color:var(--mute) }
.donate-btn{ margin:var(--s2) 0 var(--s2) }


/* ==========================================================================
   SPONSORS
   ========================================================================== */

.sponsor-wall{
  display:grid; gap:var(--s3);
  grid-template-columns:repeat(auto-fit, minmax(min(100%, 11rem), 1fr));
  align-items:center;
  margin-top:var(--s3);
}

/* One box for every logo, whatever shape the file happens to be. The three
   assets run from a square badge to a tall shield, and a shared box is what
   makes them read at the same weight instead of the tallest one dominating.
   No frame around it: the logos are already self contained marks, and boxing
   them only added a second rectangle competing with the first. */
.sponsor-tile{
  --logo-h : clamp(4.25rem, 9vw, 6rem);

  display:grid;
  /* Centred vertically, hard left horizontally. Centring on both axes left
     the mark indented from the sentence beneath it once the row stacked. The
     box stays wider than it is tall so a horizontal wordmark, which is what
     most sponsors send, has somewhere to go. */
  place-items:center start;
  flex:0 0 auto;
  inline-size:clamp(5rem, 11vw, 7.5rem);
  block-size:var(--logo-h);
}

/* Sponsor logos are dark ink on transparency, which is how brand assets
   normally arrive. They need a light ground, so keep this list on one. */
.sponsor-logo{
  /* The cap is the box's own height as a length, not a percentage. A
     percentage here is resolved against a containing block the image does not
     have, so a tall logo simply runs past the box and into the heading. */
  max-block-size:var(--logo-h);
  max-inline-size:100%;
  inline-size:auto;
  block-size:auto;
  object-fit:contain;
}

a.sponsor-tile img{ transition:transform .25s ease }
:is(a.sponsor-tile:hover, a.sponsor-tile:active, a.sponsor-tile:focus-visible) img{
  transform:scale(1.05);
}

.sponsor-list{ display:grid; gap:var(--s5); margin-top:var(--s4) }

/* Mark then words, side by side while there is room for both and stacked the
   moment there is not. The basis sits on the text rather than the logo, so
   the row breaks when the sentence would be squeezed, which is the thing
   worth protecting. A logo needs 9rem; a sentence needs a great deal more. */
.sponsor-row{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:var(--s3) var(--s4);
  padding-bottom:var(--s5);
  border-bottom:1px solid var(--hair);
}
.sponsor-row:last-child{ border-bottom:0; padding-bottom:0 }
.sponsor-row > div{ flex:1 1 22rem; min-width:0 }
.sponsor-row h3{ margin-bottom:var(--s2) }
.sponsor-row p{ color:var(--mute); max-width:56ch }


/* ==========================================================================
   FORMS
   ========================================================================== */

.form{ display:grid; gap:var(--s3) }
.field{ display:grid; gap:.45rem }
.field label{
  font-family:var(--mono); font-size:var(--t-label); font-weight:600;
  letter-spacing:.13em; text-transform:uppercase; color:var(--mute);
}
.field .opt{ text-transform:none; letter-spacing:.04em; opacity:.7 }

.field input,
.field textarea{
  font-family:var(--font);
  font-size:var(--t-body);
  color:inherit;
  background:transparent;
  border:1px solid var(--hair);
  border-radius:var(--radius);
  padding:.7rem .85rem;
  width:100%;
  resize:vertical;
}
.field input:hover,
.field textarea:hover{ border-color:currentColor }
.field input:focus-visible,
.field textarea:focus-visible{ outline:2px solid var(--c1); outline-offset:1px; border-color:transparent }

/* Honeypot. Off screen for people, still in the DOM for bots that fill in
   every field they find. */
.botcheck{ position:absolute; left:-9999px; width:1px; height:1px; opacity:0 }

/* A field that failed. There is no red in the palette, so the signal is
   carried by the words and by a butter edge on the control, which is legible
   as a shape on either ground. Colour is never the only thing saying it. */
.field.is-invalid :is(input, select, textarea){
  border-color:currentColor;
  box-shadow:inset 3px 0 0 var(--c1);
}

.field-error{
  justify-self:start;
  font-family:var(--mono);
  font-size:var(--t-label);
  font-weight:600;
  letter-spacing:.1em;
  text-transform:uppercase;
  background:var(--c1);
  color:var(--dark);
  padding:.3rem .55rem;
  border-radius:2px;
}

.form button[type="submit"]{ justify-self:start }
.form button[disabled]{ opacity:.55; cursor:progress }
.form-status:empty{ display:none }
.form-status{ margin:0; text-transform:none; letter-spacing:.02em; font-size:var(--t-small) }
/* Sent. Butter is the accent on either ground, but it can only be type on
   navy, where it is 9.70:1. On periwinkle it is 1.07:1, so the sponsors form
   read fine and the partners and add-log forms, which sit on light bands, put
   the thank you in a colour nobody could see. On light it arrives as a fill
   behind navy text instead, which is the same trade the buttons make on hover:
   same accent, legal on both grounds. Inline-block so the fill hugs the
   sentence rather than the whole column. */
.form.is-sent .form-status{
  display:inline-block;
  background:var(--c1);
  color:var(--dark);
  padding:.35em .6em;
  border-radius:var(--radius);
}
:is(.band--dark,.hero,.card--dark) .form.is-sent .form-status{
  background:transparent;
  color:var(--c1);
  padding:0;
}


/* ==========================================================================
   PARTNER ROUTES
   Three cards side by side, each one short enough that the whole set fits a
   screen. The detail that used to sit under each heading now lives in a
   <dialog> behind the button at the card's foot, so the cards stay the same
   height and nothing here scrolls.

   The cards are --line rather than solid: three filled slabs on a navy band
   would be a wall, and an inverted card would put butter labels on
   periwinkle, where butter is 1.07:1 and disappears.
   ========================================================================== */

.routes-intro{ margin-bottom:var(--s5) }
.routes-intro h2{ margin:var(--s1) 0 var(--s2) }

.route{
  display:flex; flex-direction:column; align-items:start;
  gap:var(--s2);
}
.route-num{ color:var(--c1) }
.route h3{ font-size:var(--t-h3); font-stretch:118%; text-transform:uppercase }
.route-who{ margin:0; color:var(--mute) }
.route-ask{ margin:0 }

/* Pinned to the foot so the three buttons line up across cards of unequal
   copy length. */
.route-more{ margin-top:auto }

.track-dl{ margin:0; display:grid; gap:var(--s2) }
.track-dl dt{
  font-family:var(--mono); font-size:var(--t-label); font-weight:600;
  letter-spacing:.13em; text-transform:uppercase; color:var(--c1);
  margin-top:var(--s2);
}
.track-dl dt:first-of-type{ margin-top:0 }
.track-dl dd{ margin:0; color:var(--mute) }
.track-dl dd strong{ color:inherit }


/* ==========================================================================
   MODAL
   A native <dialog>. The element keeps its own display, so nothing here may
   set one: a display on .modal would show a closed dialog. Sizing only.
   ========================================================================== */

.modal{
  width:min(42rem, calc(100vw - 2 * var(--s3)));
  max-height:min(88dvh, 46rem);
  overflow:auto;
  margin:auto;
  padding:var(--s4);
  border:1px solid var(--hair);
  border-radius:var(--radius-lg);
}
.modal h3{ font-size:var(--t-h2); font-stretch:118%; text-transform:uppercase;
           margin:var(--s1) 0 var(--s3) }
.modal::backdrop{ background:var(--dark); opacity:.8 }

.modal-foot{ display:flex; flex-wrap:wrap; gap:var(--s2); margin-top:var(--s4) }


/* ==========================================================================
   RESOURCES
   ========================================================================== */

.res-card{ display:grid; align-content:start; gap:var(--s2); padding:var(--s3) }
.res-type{
  font-family:var(--mono); font-size:var(--t-label); font-weight:600;
  letter-spacing:.13em; text-transform:uppercase;
  justify-self:start;
  padding:.3rem .55rem; border-radius:2px;
  background:var(--c1); color:var(--dark);
}
.res-summary{ color:var(--mute); font-size:var(--t-small); margin:0 }
.res-meta{ margin-top:auto; padding-top:var(--s1) }

/* An entry with no file yet. It stays visible so planned work can be
   announced, but it is not an anchor and does not invite a click. */
.res-card.is-pending{ opacity:.72 }
.res-card.is-pending .res-type{ background:transparent; color:inherit; border:1px solid var(--hair) }

a.res-card{ transition:transform .16s ease, border-color .16s ease }
a.res-card:hover{ transform:translateY(-2px); border-color:currentColor }
a.res-card:active{ transform:translateY(0); border-color:currentColor }

.res-foot{ margin-top:var(--s4) }


/* ==========================================================================
   SEASON RECORD TABLE
   ========================================================================== */

.season{ margin-top:var(--s4) }
.season-head{ display:flex; flex-wrap:wrap; align-items:baseline; gap:var(--s2) var(--s3) }
.season-head h3{ font-size:var(--t-h3); font-stretch:112% }

/* A table has a floor width it cannot go below, so it scrolls inside its own
   box and the page stays put. */
.tablewrap{ overflow-x:auto; margin-top:var(--s3) }

/* Fixed layout, so the two season tables agree on where their columns are.
   Auto layout sized each one to its own longest cell and the headings did not
   line up down the page. */
.rec{
  border-collapse:collapse; table-layout:fixed;
  width:100%; min-width:40rem; font-size:var(--t-small);
}
.rec :is(th,td):nth-child(1){ width:30% }
.rec :is(th,td):nth-child(2){ width:26% }
.rec :is(th,td):nth-child(3){ width:12% }

/* The three league meets of a season sit on one row, so the rows are few
   enough to want less air each. The floor width went up with them: the quals
   and rank cells now carry three values apiece and must not wrap. */
.rec th,
.rec td{ text-align:left; padding:var(--s1) var(--s3) var(--s1) 0; border-bottom:1px solid var(--hair); vertical-align:top }
.rec th{
  font-family:var(--mono); font-size:var(--t-label); font-weight:600;
  letter-spacing:.13em; text-transform:uppercase; color:var(--mute);
}
.rec tr:last-child td{ border-bottom:0 }
.rec td.n{ font-family:var(--mono); font-variant-numeric:tabular-nums; white-space:nowrap }
.rec td:last-child{ color:var(--c1) }


/* ==========================================================================
   ROSTER
   Three cards: the adults, the three subsystem leads, and everyone else. The
   students are first name and last initial, which is the whole reason the
   name and the role are separate elements rather than one string.

   About's h1 lives in a band rather than a page head, so it is stepped down
   to the h2 size. The outline still wants one h1 on the page; the column it
   sits in does not want it three point sizes larger than its neighbours.
   ========================================================================== */

.band h1{ font-size:var(--t-h2) }

.roster-intro h2{ margin-bottom:var(--s2) }
.roster{ margin-top:var(--s4) }
.roster-card{ display:grid; align-content:start; gap:var(--s3) }

.roster-list{ margin:0; padding:0; list-style:none; display:grid; gap:var(--s2) }
.roster-list li{ display:flex; flex-wrap:wrap; align-items:baseline; gap:var(--s1) var(--s2) }
.roster-name{ font-weight:700 }
.roster-role{
  font-family:var(--mono); font-size:var(--t-label); font-weight:600;
  letter-spacing:.13em; text-transform:uppercase; color:var(--c1);
}

.roster-dl{ margin:0; display:grid; gap:var(--s1) }
.roster-dl dt{
  font-family:var(--mono); font-size:var(--t-label); font-weight:600;
  letter-spacing:.13em; text-transform:uppercase; color:var(--c1);
  margin-top:var(--s2);
}
.roster-dl dt:first-of-type{ margin-top:0 }
.roster-dl dd{ margin:0 }


/* ==========================================================================
   SELECT, matched to the text inputs
   ========================================================================== */

.field select{
  font-family:var(--font);
  font-size:var(--t-body);
  color:inherit;
  background:transparent;
  border:1px solid var(--hair);
  border-radius:var(--radius);
  padding:.7rem .85rem;
  width:100%;
  appearance:none;
  cursor:pointer;
}
.field select:hover{ border-color:currentColor }
.field select:focus-visible{ outline:2px solid var(--c1); outline-offset:1px; border-color:transparent }
/* The open dropdown is drawn by the operating system rather than the page,
   so its colours have to be stated outright. */
.field select option{ background:var(--light); color:var(--dark) }


/* ==========================================================================
   ADD LOG
   ========================================================================== */

.hint{ font-size:var(--t-small); color:var(--mute); line-height:1.4 }
.mt{ margin-top:var(--s4) }

.json-out{
  border:1px solid var(--hair);
  border-radius:var(--radius);
  padding:var(--s3);
}
.json-head{
  display:flex; flex-wrap:wrap; align-items:center;
  gap:var(--s2); justify-content:space-between;
  margin-bottom:var(--s2);
}
.json-copy{ padding:.5rem .9rem; font-size:var(--t-label) }

/* Preformatted text scrolls inside its own box, so a long id cannot widen
   the page. */
.json-body{
  margin:0;
  overflow-x:auto;
  font-family:var(--mono);
  font-size:var(--t-small);
  line-height:1.5;
  white-space:pre;
  tab-size:2;
}


/* ==========================================================================
   SPONSOR PITCH
   ========================================================================== */

/* The denominator in a figure like 156/8,365. It is context for the number
   beside it, so it stays quieter. */
.stat-sub{
  font-size:.42em;
  font-weight:700;
  font-stretch:100%;
  letter-spacing:0;
  opacity:.6;
  margin-left:.1em;
}

.benefit-grid{ margin-top:var(--s4) }
.benefit-grid h3{ margin-bottom:var(--s2) }

.contact-dl{
  margin:var(--s4) 0 0;
  display:grid; gap:var(--s2) var(--s3);
  grid-template-columns:repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
}
.contact-dl dt{
  font-family:var(--mono); font-size:var(--t-label); font-weight:600;
  letter-spacing:.13em; text-transform:uppercase; color:var(--c1);
  margin-bottom:.3rem;
}
.contact-dl dd{ margin:0 0 var(--s2); word-break:break-word }


/* ==========================================================================
   PLACEHOLDER, remove as each chunk lands
   ========================================================================== */

.todo{
  border:1px dashed var(--hair);
  border-radius:var(--radius);
  padding:var(--s4);
  font-family:var(--mono);
  font-size:var(--t-small);
  color:var(--mute);
}


/* ==========================================================================
   PHONES
   The second width breakpoint, and like the footer's it buys arrangement, not
   size. The bar stays one line tall here, which it cannot do while carrying
   the mark, the wordmark and the tags at once: the wordmark goes, the tags
   close up, and the nav takes what the mark leaves of the row instead of a row
   of its own. --header-h needs no restating for that reason, since the bar is
   the same one row here as everywhere else, so nothing jumps when the header
   lands.

   The footer is here for the opposite reason. It is not rearranged at all: it
   keeps the brand left, the marks right and the strip centred underneath, the
   same as every wider screen. What it cannot keep is the full size of those
   parts, because at 320px the row is 74px short of holding them.
   ========================================================================== */

@media (max-width:37rem){

  :root{
    /* A smaller mark, because the five tags need the width more than the mark
       does, and on this side of the query the mark is the whole visible brand.
       --header-h derives from this, so the bar and the space reserved for it
       shrink together, and there is still nothing to keep in sync by hand. */
    --logo-h:40px;
  }

  /* The mark already says whose site this is, and it is the half of the lockup
     still legible at this size. Hidden the long way rather than with
     display:none: the image carries empty alt, so this wordmark is the whole
     accessible name of the link home, and a screen reader still has to read
     it. */
  .site-head .brand span{
    position:absolute;
    width:1px; height:1px;
    overflow:hidden;
    clip-path:inset(50%);
    white-space:nowrap;
  }

  /* Tighter tags, and a basis of zero so the nav takes whatever the mark
     leaves of the bar's line rather than asking for a line of its own. Past
     the width where even the tight row fits it scrolls, which it was already
     set up to do, and the auto margin holding the tags right collapses on its
     own once there is no spare room to hold them against. */
  .site-nav{ gap:var(--s1); flex-basis:0 }

  /* One step under --t-small's floor, and the last of the three savings the
     row needs. Fixed rather than fluid because every clamp in the file has
     bottomed out by this width, so there is nothing left to scale against.
     Measured: the five tags need 263px, and a 375px screen, which is the SE,
     leaves them 287px. Below about 350 they scroll instead, as before. */
  .site-nav a{ font-size:.82rem }

  /* The two calls to action share the row instead of stacking: equal halves,
     so they are always side by side, and a label that will not fit wraps
     inside its own button rather than pushing the pair onto two rows. Tighter
     side padding buys most of the width back, and the type stays at full size,
     because these two are the page's primary actions. */
  .hero-actions{ gap:var(--s1) }
  .hero-actions .btn{
    flex:1 1 0;
    padding-inline:1rem;
    text-align:center;
    line-height:1.15;          /* room for a second line when one is needed */
  }

  /* The footer keeps its one row here rather than stacking: brand left, marks
     held right, strip centred underneath, which is the arrangement every wider
     screen gets. What changes is the size of the parts, below.

     Measured: at full size the brand is 125px and the five marks 220px, which
     with the gap between them needs 362px. An SE leaves 343 and a 320 screen
     leaves 288, so something has to give, and it is shared between the two
     rather than taken out of either alone. The tap targets keep the 24px floor
     that makes them targets at all, with room over it. */
  .site-foot .brand span{ font-size:clamp(.72rem, 3.4vw, 1rem) }
  .site-foot .brand img{ width:clamp(28px, 8.5vw, 40px) }

  .site-foot .foot-social{
    --hit  : clamp(1.9rem, 9.4vw, 2.75rem);
    --mark : clamp(1.05rem, 5.2vw, 1.375rem);
  }

  /* The strip on one line, which is what it takes. Measured at the size above:
     the three items need 429px and an SE offers 343, so roughly a fifth has to
     come out. It comes out of three places at once rather than all out of the
     type: the gap between the items drops a step, the tracking loosens less,
     and the size scales with the viewport instead of sitting on its floor.

     This does go under the floor the base rule calls legible mono, knowingly.
     It is 7.5px on an SE and 6.4px at 320. Nothing here is meant to be read at
     a glance, it is a copyright notice and two contact strings that are also
     links, and the address and number are both reachable from the marks above
     at full size. If it ever reads as too small, this block is the one to
     loosen, and the strip goes back to wrapping onto two lines. */
  .site-foot .foot-legal{
    font-size:clamp(.4rem, 2vw, .58rem);   /* .58rem is what the base rule gives here, so the two meet at the query edge rather than stepping */
    letter-spacing:.06em;
    column-gap:var(--s2);
  }
}


/* ==========================================================================
   PREFERENCES. Not a breakpoint, a user setting.
   ========================================================================== */

@media (prefers-reduced-motion:reduce){
  *{ animation:none !important; transition:none !important }
}
