/* ============================================================================
 * base.css - the chrome every page in this concept shares.
 *
 * It was inline in index.html, and course.html had its own private copy of an
 * older generation of it. That copy drifted a whole palette behind without
 * anything noticing: every grey in it is still the cold set, --acid-ink does
 * not exist there, and it has no light theme at all, which is now the only one.
 * A third page would have drifted the same way inside a month.
 *
 * So the WHOLE stylesheet moved here rather than a hand-picked subset. Picking
 * out "the shared bits" is a judgement made once and then quietly wrong, and it
 * is exactly the judgement that produced the drift. A page that only needs the
 * bar still downloads the hero rules, which costs a few kilobytes over gzip and
 * buys the guarantee that the tokens are the same everywhere. Page-specific
 * rules go in that page's own <style>, after this file.
 * ==========================================================================*/

/* ============================================================ the ground
 *
 * A dark, technical register, deliberately nothing like the live site: that
 * one is a cream editorial sheet with a serif and an isometric drawing. This
 * is graphite, a grotesque, and an acid accent that reads as a screen colour
 * rather than an ink colour, because the subject is a definition running,
 * not a drawing sitting still.
 *
 * Inside a page the accent means one thing: the live value. Anything the
 * machine is computing, or that the visitor is about to act on, is acid.
 * Everything else is grey. Two accents would make it decoration.
 *
 * AND ACID IS THE BRAND. The identity (docs/identity/identity.html, decided 14 Sep 2026)
 * made #C8F44E the colour House of Parameters is recognised by, so it is also
 * allowed to be a GROUND: a whole field of acid with ink on it. On this site that
 * is the favicon. The foot was acid for a day and went back to the surface
 * grey, Jad's call. It is still one hue, and it is still never ink on paper:
 * see --acid-ink below.
 */
:root{
  --card-shadow:0 1px 2px rgba(19,22,16,.05),0 12px 28px -14px rgba(19,22,16,.24);
  /* ONE THEME, AND IT IS PAPER. There was a graphite palette here and a switch
     in the bar to reach it; both are gone, so these are the only values and
     nothing has to be kept in step with a second set any more.

     THE ACCENT'S OWN HUE, drained. Every grey here was once mixed towards blue,
     which is the default a screen gives you for free and the reason the page
     read as monochrome even though nothing on it was actually grey: a blue-grey
     next to a chartreuse is two cold things, and the eye files the pair as one
     absence of colour. Pulling towards yellow fixed the coldness and read as
     beige. So the scale is pulled towards the accent itself and drained almost
     to a neutral: the acid sits inside its own family rather than against it.

     A WHITE ground and a faint sage under it. The page was bone, and bone is
     yellow, which is what made #F1EDE3 read as beige on every card, every band
     and the whole footer.

     The STEPS are the part not to lose. Each is measured against the ground it
     sits on rather than picked by eye, because a palette rebuilt by eye on a
     light ground loses its quietest step first, and the quietest step here is
     the band that separates two sections: band 1.078, card 1.165, line 1.347. */
  --bg:#FFFFFF;
  --bg2:#F5F7F3;
  --surface:#EBEFE7;
  --surface2:#DDE3D7;
  --line:#DAE0D4;
  --line2:#B4BDAC;
  --ink:#131610;
  --ink2:#575E51;
  /* --ink3 carries every 11px label on the site: which Rhino versions a script
     supports, every lesson duration, the section eyebrows. It was chosen to
     look quiet and measured 3.35:1, against the 4.5:1 that 11px text needs.
     Quiet is a legitimate thing to want and 3:1 is not the way to get it; size
     and weight are. This clears 4.5:1 on the WORST ground it is used on. */
  --ink3:#555C4F;
  --acid:#C8F44E;
  --acid-dim:#5E7A16;
  --blue:#5B8CFF;

  /* The one block that inverts. A page of bands in the same family is rhythm
     but it is still one colour; one section in a deep olive is the page saying
     something different, and it is spent on the only thing here you can buy
     today. The acid is at home on it, because it is the same hue with the
     light taken out. */
  --deep:#141810;
  --deep2:#1B2014;
  --deep-line:#2C3220;
  --deep-line2:#3D452E;
  --deep-ink:#EFF2EA;
  --deep-ink2:#A6AE9A;
  --deep-ink3:#879377;

  /* The four the CANVASES need, as bare channels so the drawing code can read
     them out and lerp between them. Everything on a canvas is one of these:
     the structural line, the accent, the ink a node is drawn in, and the colour
     a canvas fills with to hide what is behind it. */
  /* The accent does two jobs and they part company on a light ground. As a
     FILL it is #C8F44E - the waitlist button is acid with dark type on it,
     because it is the one thing you are meant to press. As INK - a letter, a
     hairline, a focus ring - #C8F44E on white is about 1.2:1 and simply is not
     there, so ink gets its own darker green and the fill keeps the acid. */
  --acid-ink:#4A6410;

  --c-line:128,137,124;
  --c-acid:104,134,26;
  --c-node:19,22,16;
  /* the canvases fill with --bg2, so this follows it */
  --c-back:245,247,243;

  /* THESE ARE NOT A THEME, and they have to be defined here rather than beside
     the colours. They lived in the light block only, where they were invisible
     as a bug because the light page is the one that looked right: on graphite
     every one of them was simply undefined, so var(--display) fell back to
     Times New Roman across the whole page, var(--gut) took every gutter to
     zero - which is why the bar changed height when the theme was switched -
     and var(--t2) left the transitions off. A token used by both themes
     belongs to neither. */
  --display:'Space Grotesk',ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif;
  --mono:'JetBrains Mono',ui-monospace,Consolas,'Cascadia Mono',monospace;

  /* Two curves and four durations, and everything on the page uses them. The
     expo curve is what makes a move read as deliberate rather than linear;
     the CSS built-ins are all too weak to notice. */
  --ease:cubic-bezier(0.16,1,0.3,1);
  --ease-io:cubic-bezier(0.76,0,0.24,1);
  --t1:180ms;--t2:340ms;--t3:620ms;--t4:1100ms;

  --gut:clamp(20px,4.4vw,64px);

  /* THE BAR'S HEIGHT, published, because four other things need to know it and
     every one of them was guessing. The bar is padding-driven, so it measures
     differently on a phone and a desktop, and the hero, the scroll padding and
     the deep-block flip all have to reserve whatever it actually is. It is
     redefined in the coarse block below, next to the rules that set it. */
  --bar:74px;

  /* THE CONTROL RADIUS. There were exactly two radii on this site, 3px on
     fifteen surfaces and 999px on twenty-seven controls, with nothing between
     them: a pill is a shape with an opinion, and next to a 3px cover plate it
     read as two design systems sharing a bar. One number for every control,
     tighter in the hand, where a control is larger relative to everything
     around it and a full pill is that much louder. */
  --r-ctl:10px;

  /* ---- the reel, section 03 -------------------------------------------
     The reel is the live site's hero, ported whole, and it reads its colours
     off :root by name rather than carrying any of its own. These are those
     names, re-pitched: the live site is a cream editorial sheet with an amber
     accent, and none of that belongs here, so the greys come from this page's
     own scale and --amber is bound to the acid. That keeps the site's one rule
     intact - a second accent colour would make the accent decoration - and it
     is why the reel reads as part of this page rather than a window onto
     another one. --page is the ground the drawing fades out against, so it has
     to be the real page colour or the vignette leaves a halo. --amber goes to
     the acid's INK value, not its fill: #C8F44E on white is not a line, it is a
     rumour. */
  --page:#FFFFFF;--paper:#F5F7F3;
  --surface:#EBEFE7;--surface2:#DDE3D7;--surface3:#D2DACB;
  --rule:#DAE0D4;--rule2:#B4BDAC;
  --amber:#4A6410;--amber-bg:#E8F3C9;
  /* The neutral cells follow the ground into the sage. cell1 is the acid tint
     and cell2 was already a pale green, so both were nearly there. */
  --cell1:#E8F3C9;--cell2:#E3EADF;--cell3:#EDF0E9;--cell4:#F7F9F4;--cell5:#E5EADE;
  --ghost:#EAEEE5;--ghost2:#FAFCF8;--road:#DFE5D8;
  --tree:#C3D2B4;--tree2:#A9BC97;
  --glass:#E4ECEE;--glass2:#CBDADE;--glass3:#B0C4CB;--glass4:#8AA5B0;
  --timber:#E0CCA6;--timber2:#C7A97C;--timber3:#A2855D;
}

*{box-sizing:border-box}
/* The grey box iOS paints over anything tappable. Every interactive thing here
   already says what it is doing with its own hover and active states. */
html{-webkit-tap-highlight-color:transparent}
/* Every fragment link on every page, in one rule on the scroll container,
   rather than a scroll-margin on each target: there are section ids, the eight
   <details> rows on scripts.html that index.html links to by slug, and anything
   that ever calls scrollIntoView. Measured before: #questions lost all 18px of
   its eyebrow and 27px of its heading, and scripts.html#sheet-setter landed the
   whole 28.6px title strip under the bar. */
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%;
  scroll-padding-top:calc(var(--bar) + 16px)}
body{
  margin:0;background:var(--bg);color:var(--ink);
  font-family:var(--display);font-weight:400;line-height:1.55;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}

img{max-width:100%;display:block}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
::selection{background:var(--acid);color:#0A0B0D}

:focus-visible{outline:2px solid var(--acid-ink);outline-offset:3px;border-radius:2px}
/* A section a link has just scrolled to takes focus so Tab carries on from
   there (base.js), and a ring round an entire section says nothing. */
[tabindex="-1"]:focus{outline:none}

/* The gutter, or the notch, whichever is bigger. In landscape on a phone with
   a rounded corner or a camera cutout, a plain gutter puts the first character
   of every line underneath it. */
.wrap{width:100%;max-width:1560px;margin:0 auto;
  padding-left:max(var(--gut),env(safe-area-inset-left));
  padding-right:max(var(--gut),env(safe-area-inset-right))}

/* ---- type scale ----
 * Display sizes are fluid and tight. Anything above ~40px gets negative
 * tracking, because a grotesque at size sets loose by default and reads as
 * unset rather than as large. */
/* Smaller than it was, because the headline now names the thing being taught
   instead of gesturing at it, and "Learn Grasshopper" is seventeen characters
   where "Geometry" was eight. At the old 11.5vw the longest line overran the
   measure on a 1440 screen. Used by the hero h1 and nothing else. */
.h-xxl{font-size:clamp(34px,8.4vw,132px);line-height:.92;letter-spacing:-.04em;font-weight:500;margin:0}
/* And it stops at the middle of the screen. The longest line, "Learn
   Grasshopper", sets 8.42em in Space Grotesk, measured, so the size whose
   right edge lands exactly on 50vw is (50vw - gutter) / 8.42, which comes out
   at 5.42vw at every width the gutter is not clamped. The max-width is the
   guarantee rather than the mechanism: the size is what does it, and the
   fallback face sets 7.83em, narrower and not wider, so a missing webfont ends
   the line short of the middle and can never wrap it.

   Only above the breakpoint where the hero is two columns. Half a phone screen
   is a column, not a headline. */
@media(min-width:901px){
  .h-xxl{font-size:clamp(34px,5.42vw,168px);max-width:calc(50vw - var(--gut))}
}
.h-xl{font-size:clamp(38px,7.4vw,116px);line-height:.94;letter-spacing:-.04em;font-weight:500;margin:0}
.h-l{font-size:clamp(28px,4.1vw,64px);line-height:1.02;letter-spacing:-.032em;font-weight:500;margin:0}
.h-m{font-size:clamp(21px,2.3vw,34px);line-height:1.16;letter-spacing:-.022em;font-weight:500;margin:0}
.lede{font-size:clamp(16px,1.42vw,22px);line-height:1.5;color:var(--ink2);font-weight:300;margin:0}
.body{font-size:clamp(15px,1.06vw,17px);line-height:1.68;color:var(--ink2);font-weight:300;margin:0}
/* Emphasis here is INK, not weight. The body is 300 on --ink2, and a browser's
   default 700 lands like a shout in a paragraph set that light; full ink at 500
   says the same thing in the voice the rest of the page is using. */
.body strong,.body b{font-weight:500;color:var(--ink)}

.mono{font-family:var(--mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--ink3);font-weight:400}
.acid{color:var(--acid-ink)}

/* A section is announced by its number and its name on one rule. Numbering is
   what turns a scroll through blocks into a document with an order. */
.eyebrow{display:flex;align-items:center;gap:14px;margin:0 0 clamp(26px,3.6vw,58px)}
.eyebrow::after{content:'';flex:1;height:1px;background:var(--line)}
.eyebrow b{font-weight:400;color:var(--acid-ink)}

section{position:relative}
/* THE FLOOR, not the ratio. 11vw is 42.9px on a 390px phone, so this clamp
   pinned to its 84px floor at both ends of every section: a 168px empty band
   at each of seven joints, on a screen 844px tall. The floor was a desktop
   minimum doing duty as a phone maximum. Only the floor moves, so 11vw still
   governs from 546px up and every desktop width is byte for byte what it was.
   Measured at 390: joints of 125 to 142px become 77 to 94. */
.pad{--pt:clamp(60px,11vw,180px);padding:var(--pt) 0 clamp(60px,11vw,180px)}
/* WHERE A LINK TO A SECTION LANDS. The scroll stops at the section's top edge,
   and the top edge is up to 180px of padding above the first word, so Courses
   from the bar put the catalogue's heading a third of the way down the screen.
   Each section's top padding is the variable --pt, which is set wherever a
   section takes a shorter one, and the margin hands that padding back so the
   eyebrow lands just under the bar at every width. It is a scroll-margin and
   not script, so a link arriving from another page lands the same way as a
   click on this one, and base.js reads it for the eased scroll. */
section[id]{scroll-margin-top:calc(12px - var(--pt,0px))}
/* THE CATALOGUE RUNS STRAIGHT INTO "HELP WITH A PROJECT", and that section's
   own dark band is what marks the change of section, so the catalogue does not need its
   full pad underneath as well. The scripts follow the band and take a shorter
   pad for the same reason: the edge of the band already separates them. */
#courses{padding-bottom:clamp(32px,4.4vw,72px)}
/* AND ABOVE IT THE PREMISE, which already ends on a pad of its own, so the
   catalogue takes the same shorter top the other continuous joints take. The
   premise's argument and the shelf that answers it read as one run rather than
   two islands. Measured before, from the end of the premise to the catalogue's
   eyebrow: 100px on a phone, 217 at 1440, 252 at 1920. */
#courses{--pt:clamp(40px,5vw,80px)}
#scripts{--pt:clamp(40px,5vw,80px)}
/* And 03 to 04 takes the same joint, so the catalogue, the scripts and what you
   build read as one continuous run rather than three islands. Two pads back to
   back here were another 317px on a desktop and 120 on a phone. The next
   section, who teaches it, sits on its own band and keeps its full pad. */
#scripts{padding-bottom:clamp(32px,4.4vw,72px)}
#build{--pt:clamp(40px,5vw,80px)}

/* ============================================================ the cursor
 *
 * A dot that is exactly where the pointer is, and a ring that is always a
 * little behind it. The lag is the whole effect: it is what makes the ring
 * feel like a physical thing being dragged rather than a second pointer.
 * Fine pointers only, and never under reduced motion: a lagging ring is
 * precisely the sort of movement that setting is asking to be spared.
 *
 * It is the olive every other acid line on the page takes, with no blend
 * mode. It used to sit in difference, which returns the accent unchanged over
 * near-black and was right for as long as the page was graphite; over white it
 * returns the accent's inverse, a hard blue-purple that appears nowhere else in
 * the palette. Olive still crosses the waitlist button legibly, because olive
 * on chartreuse is a real contrast rather than a coincidence.
 */
/* A phone has no cursor to chase, and these two sat at -19px and -3px off the
   left edge of every page, which is a horizontal overflow nobody can see and
   two elements the compositor keeps alive for nothing. */
@media(pointer:coarse){#cur,#curdot{display:none}}
#cur,#curdot{position:fixed;top:0;left:0;z-index:180;pointer-events:none;
  border-radius:50%;opacity:0;transition:opacity var(--t2)}
#curdot{width:6px;height:6px;background:var(--acid-ink);margin:-3px 0 0 -3px}
#cur{width:38px;height:38px;border:1px solid var(--acid-ink);margin:-19px 0 0 -19px;
  transition:opacity var(--t2),width var(--t2) var(--ease),height var(--t2) var(--ease),
    margin var(--t2) var(--ease),background var(--t2)}
body.cursor-on #cur,body.cursor-on #curdot{opacity:1}
body.cursor-hot #cur{width:74px;height:74px;margin:-37px 0 0 -37px;background:rgba(200,244,78,.14)}

/* ============================================================ the bar */
.bar{position:fixed;top:0;left:0;right:0;z-index:120;
  display:flex;align-items:center;justify-content:space-between;
  padding:18px max(var(--gut),env(safe-area-inset-right)) 18px max(var(--gut),env(safe-area-inset-left));
  transition:background var(--t2),color var(--t2),padding var(--t2) var(--ease)}

/* THE BAR IS GLASS, so what is under it decides what it should be. Over the
   inverted block it was a pale wash on a near-black ground with dark ink in it,
   which is the one place on the site where the wordmark and the menu simply
   vanished. It borrows the block's own six names for as long as it is over one.
   body.deep is set in base.js from the same scroll listener as body.scrolled. */
body.deep .bar{
  --bg:var(--deep);
  --line:var(--deep-line);
  --line2:var(--deep-line2);
  --ink:var(--deep-ink);
  --ink2:var(--deep-ink2);
  --ink3:var(--deep-ink3);
  --acid-ink:var(--acid);
  color:var(--deep-ink)}
/* BLUR ALONE, no rule. The hairline was doing the job the blur already does,
   twice, and on a phone it cut a hard line across a page whose whole ground is
   soft. */
body.scrolled .bar{background:color-mix(in srgb,var(--bg) 72%,transparent);
  padding-top:12px;padding-bottom:12px;
  backdrop-filter:blur(18px) saturate(1.4);-webkit-backdrop-filter:blur(18px) saturate(1.4)}
.mark{font-size:17px;display:flex;align-items:center}
/* THE WORDMARK, and the frame is its H: an H whose crossbar is a roof, so the
   space under it is a house, followed by "ouse of Parameters" in Chakra Petch
   700. It is one outlined path, drawn by tools/ops/outline-wordmark.ts from the
   font file, so three words cost no webfont. It replaced a frame 1.2em tall
   standing beside the name in Space Grotesk, which read as two things placed
   side by side; this reads as one word.

   Sized in em so the bar's 17px and the footer's 19px each get it in
   proportion, and filled with currentColor so it follows the deep block and the
   foot the way text would. The viewBox is exactly the cap line to the baseline,
   because nothing in the name rises above one or falls below the other, so
   .82em IS the cap height: 14px in the bar, 199px wide, which leaves the links
   the room they had at the 680px breakpoint. */
.wordmark{height:.82em;width:auto;flex:none;fill:currentColor;display:block}
/* The pulsing dot that used to sit before the wordmark is gone, markup and
   all. A mark that blinks is asking for attention it already has, and it was
   the only thing on the page animating for no reason of its own. */
.barnav{display:flex;align-items:center;gap:clamp(14px,2vw,34px)}
.barlink{font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink2);position:relative;padding:6px 0}
/* NOTHING MOVES ON HOVER, since 14 Sep 2026: Jad's call, for the whole site
   but one place, the catalogue's row of courses, which is his call too and
   says why where it is styled. A link says it is a link by its colour changing. There was an acid underline here that swept
   in from the left, and it went with the rest of the motion. */
.barlink{transition:color var(--t1)}
@media(hover:hover){.barlink:hover{color:var(--ink)}}
/* NO MENU, SO THE LINKS ARE THE NAVIGATION. There was a menu button and a
   full-screen sheet behind it, and on a phone that sheet was the only way to a
   section. Without it the links stay in the bar for as long as they fit beside
   the wordmark, and below that the bar is the wordmark alone: on a phone the
   page is one scroll, and the sections are simply what comes next. Measured
   with four links: under 640 they pushed the wordmark onto two lines, and at
   680 they left it 54px of air.

   760 WITH FIVE. "Upcoming" joined the bar when the workshops did, since the
   dates are what the site is for now, and it is about 95px of link and gap.
   The wordmark (199px), the five links and the two gutters come to about
   692px at 760, the same air as before; at 700 they would touch. */
@media(max-width:760px){.barnav .barlink{display:none}}

/* ============================================================ the hero */
/* The bottom padding clears the scroll cue. At the cue's own offset the two
   sat in the same band and the word "Scroll" printed straight through the
   first stat's label. */
/* PADDING-TOP, because nothing reserved the bar and the headline was printed
   straight through it. min-height is only a floor and the content is held at
   the bottom, so on a tall screen this changes nothing at all; on a short one
   the hero simply grows past the viewport, which is what clears the bar.
   Measured overlap before, at 390 wide: 71.6px at 664 tall and below, 40.7px at
   700, 20.7px at 720, clear only at 741. A phone showing its address bar is
   well under that, and so is a 1440x720 laptop, which is why this is not behind
   a width query. */
.hero{padding-top:calc(var(--bar) + 32px);min-height:100svh;display:flex;flex-direction:column;justify-content:flex-end;
  position:relative;overflow:hidden;padding-bottom:clamp(64px,7vw,104px)}
#field{position:absolute;inset:0;width:100%;height:100%;z-index:0;
  transition:opacity 1200ms ease}
.js #field{opacity:0}
.js #field.on{opacity:1}
/* The field is drawn edge to edge, then faded out at the bottom so the type
   sits on darkness rather than on a lattice. A gradient does this better than
   drawing less would: the geometry stays whole and only the light goes. */
.hero::after{content:'';position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(180deg,
    color-mix(in srgb,var(--bg) 55%,transparent) 0%,
    transparent 26%,
    transparent 44%,
    var(--bg) 96%)}
.heroin{position:relative;z-index:2;width:100%}

/* The two tool marks. Sized by height, because the two artboards are both
   300 x 300 and only their height reads as the same size to the eye. */
/* The credential sits between the headline and the two marks, and it starts
   where the headline starts. It had the eyebrows' acid rule running into it,
   which was the mistake: a rule plus its gap is 46px of indent, so the one line
   on this screen that is a claim about the headline was the only line not
   lined up with it. Flush left it reads as the headline's second breath.

   It is also bigger than the mono elsewhere. Eleven pixels is right for a
   section number, which is a label nobody needs to read; this is a sentence
   somebody should, and at 11px it sat under the title as a caption for the two
   logos beneath it. The tracking comes in as the size goes up, because .16em is
   what makes 11px legible and what makes 15px sprawl. */
/* The same clamp trap as the section rhythm, the other way up: 2.3vw is 9px
   on a phone, so this pins to a 20px floor under a headline that is 100px
   tall there, and the credential reads as part of the title rather than as
   the line after it. Only the floor moves; 1440 still lands on its 32px cap. */
.credit{color:var(--ink2);margin:clamp(26px,2.3vw,32px) 0 0;
  font-size:clamp(12px,1.05vw,15px);letter-spacing:.1em;line-height:1.5}
/* The title itself carries the ink and the words around it stay quieter, so the
   line reads as one thing rather than as a run of shouted capitals. */
.credit b{font-weight:400;color:var(--ink)}

.tools{display:flex;align-items:center;gap:clamp(14px,1.6vw,24px);
  margin:clamp(20px,1.7vw,22px) 0 0}
.tools img{height:clamp(30px,3vw,44px);width:auto;display:block}

/* Each line of the headline is a mask with the words inside it. The words
   travel up out of the mask; the mask itself never moves. That is what makes
   the letters appear to be uncovered rather than to fly in. */
/* The mask needs room for descenders. At line-height .92 the clip box ends
   just below the baseline, so overflow:hidden was slicing the tail off every
   y, g and p in the headline, most visibly the two in "way" and "actually".
   The padding gives them room inside the box; the negative margin takes the
   same amount back out of the layout, so the three lines sit exactly where
   they did and the h1 is the height it was. */
.line{overflow:hidden;display:block;padding-bottom:.18em;margin-bottom:-.18em}
.line > span{display:block;transition:transform var(--t4) var(--ease)}
/* A drawn circle, not the font's full stop. Whichever face the page ends up
   with decides the shape of a period: Space Grotesk sets an oval one and
   the fallback sets a rounded square, and this is the one mark on the page
   that has to read as a point. aria-hidden: it is punctuation, not content. */
.dot{display:inline-block;width:.13em;height:.13em;border-radius:50%;
  background:var(--acid);margin-left:.055em;vertical-align:baseline}
/* Held below its mask only when there is a script to raise it. Ungated, the
   headline of the site sat permanently outside its own overflow with scripts
   off: an invisible h1, which is the worst thing on the page to lose.
   130% and not 105%: the clip box is now taller than the span by the descender
   padding, and at 105% the tops of the letters stayed showing above the mask. */
.js .line > span{transform:translateY(130%)}
body.lit .line > span{transform:translateY(0)}
.line:nth-child(2) > span{transition-delay:90ms}
.line:nth-child(3) > span{transition-delay:180ms}

/* No rule under the headline. The space alone separates the statement from the
   figures under it, and a hairline there cut the hero into two stacked panels
   instead of leaving it as one. The padding stays: it was holding the gap, not
   just clearing the line, so removing the border must not close it up. */
.herometa{display:flex;flex-wrap:wrap;gap:clamp(18px,3vw,54px);align-items:flex-end;
  justify-content:space-between;margin-top:clamp(26px,3.6vw,52px);
  padding-top:clamp(18px,2.2vw,30px)}
.stats{display:flex;flex-wrap:wrap;gap:clamp(18px,3.2vw,56px)}
.stat b{display:block;font-size:clamp(20px,2.1vw,30px);font-weight:500;letter-spacing:-.02em;
  color:var(--ink);font-variant-numeric:tabular-nums}
.stat span{display:block;margin-top:3px}
/* Wider than a reading measure now: the column holds the signup as well as the
   sentence, and at 40ch the field and its button wrapped onto two rows. */
.heroright{max-width:46ch}
/* Two sentences on two lines, by a <br>. On a phone the first is a few pixels
   too long for the column and left "designers." alone; balanced, it breaks
   after "architects," instead. A desktop, where it fits, is unchanged. */
.heroright .body{text-wrap:balance}

/* The signup, sized for the hero column instead of for a centred section. It is
   the same form, the same endpoint and the same handler as the one at the foot
   of the page. Only the alignment and the scale differ, so a visitor who acts
   in the first screen and one who acts in the last are doing the same thing. */
.form.inline{max-width:none;margin:20px 0 0}
/* 16px, and it is not a taste decision: iOS Safari zooms the entire page when
   a focused input is under 16px, and it does not zoom back out afterwards. */


.form.inline + .sent{margin-top:12px}
/* THEY CANNOT SHARE A ROW ON A PHONE. The field and the button want about
   400px between them and the hero column gives 350, so the row wrapped and
   left a full-width field above a button two thirds its width, aligned on
   neither edge nor height. Below the width where they fit they stack on
   purpose: one width, one height, one radius, so it reads as one control in
   two parts rather than as a wrap that got away. 52.8 and 46.6 become 48 and
   48, smaller than either was and still above the 44 a thumb needs. */
/* min-width:0 on the COLUMN. .heroright is a flex item, so its min-width is
   auto, which means its min-content width, and the control's min-content is a
   44px button plus whatever the field insists on. Without this the column
   refused to shrink below that sum inside a 350px page and the button ran off
   the right edge, where the hero's overflow:hidden swallowed it and nothing
   scrolled sideways to give it away. */
@media(max-width:640px){
  .heroright{min-width:0}
}

.cue{position:absolute;left:var(--gut);bottom:clamp(22px,3.4vw,50px);z-index:2;
  display:flex;align-items:center;gap:10px}
.cue u{display:block;width:34px;height:1px;background:var(--line2);position:relative;
  overflow:hidden;text-decoration:none}
.cue u::after{content:'';position:absolute;inset:0;background:var(--acid);
  animation:sweep 2.4s var(--ease-io) infinite}
@keyframes sweep{0%{transform:translateX(-100%)}60%,100%{transform:translateX(100%)}}

/* The scrolling strip of disciplines that used to sit under the hero is gone,
   and with it the acid-tinted band that was the top third's only colour event.
   Nothing replaces it: the hero now runs straight into the premise. If that
   stretch ever reads as too flat again, the fix is a ground on a section rather
   than another marquee, because a list of twelve words moving sideways is a
   thing to look at rather than a thing to read. */

/* ============================================================ upcoming
 *
 * The workshops, straight after the hero, because they are the one thing on
 * the page a visitor can book today. Each is a row, not a tile: the dates
 * first in the largest type in the section, since the dates are what somebody
 * decides on; then what it is and where; then the call. On a surface with a
 * hairline, the same object the script cards are, because a workshop is a
 * thing on offer and not a section of argument.
 *
 * It takes the premise's old place against the hero, so it takes the top pad
 * the premise had there, and the premise moves to the shorter joint below it.
 */
#upcoming{--pt:clamp(64px,7vw,120px);padding:var(--pt) 0 clamp(32px,4.4vw,72px)}
.events{display:flex;flex-direction:column;gap:clamp(12px,1.4vw,18px)}
.event{display:grid;grid-template-columns:minmax(150px,.5fr) minmax(0,2fr) auto;
  gap:clamp(20px,3vw,52px);align-items:center;padding:clamp(20px,2.4vw,34px);
  background:var(--surface);border:1px solid var(--line);border-radius:3px}
.event[hidden]{display:none}
.ewhen b{display:block;font-size:clamp(34px,4.2vw,64px);font-weight:500;letter-spacing:-.04em;
  line-height:1;font-variant-numeric:tabular-nums}
.ewhen span{display:block;margin-top:12px}
.ekind{margin:0}
.ewhat h3{margin:8px 0 clamp(16px,1.8vw,22px);font-size:clamp(22px,2.2vw,32px);font-weight:500;
  letter-spacing:-.022em;line-height:1.1}
.ewhat h3 a{transition:color var(--t1)}
@media(hover:hover){.ewhat h3 a:hover{color:var(--acid-ink)}}
/* The terms are the ones "help with a project" uses, a size down: three of them sit
   side by side here, and "Monday to Friday, 7 to 9 pm" is a long value. */
.event .term b{font-size:clamp(16px,1.4vw,20px)}
.ebook{display:flex;flex-direction:column;align-items:flex-start;gap:12px}
.eventsnone{margin:0;max-width:56ch}
.eventsnone a{color:var(--ink);text-decoration:underline;text-decoration-color:var(--acid-ink);
  text-underline-offset:3px}
@media(max-width:900px){
  .event{grid-template-columns:1fr;gap:18px}
  .ebook{flex-direction:row;align-items:center;flex-wrap:wrap;gap:14px 18px}
}

/* ============================================================ the statement */
/* THREE LINES, SET: "An afternoon of Grasshopper / saves a week. / Or costs you
   two!", the first sentence on two and the reversal on one. The breaks are in
   the markup and nowrap holds them, so the size has to be the thing that
   gives. The longest line is 13.16em, measured, so the size is the smaller of
   the page's own heading size and the column's width over 13.3: 50px on a wide
   screen, 26px on a phone, and never a fourth line at any width between. The
   column is the container, which is why it is set there rather than off the
   viewport, where the gutter and the globe beside it would each need a term. */
.saygrid > div:first-child{container-type:inline-size}
.say{white-space:nowrap;font-size:min(clamp(28px,4.1vw,64px),calc(100cqi / 13.3))}
/* the two figures under the premise */
.tally{display:flex;gap:clamp(26px,3.6vw,54px);margin-top:clamp(26px,3vw,40px)}
.tally b{display:block;font-size:clamp(28px,3.2vw,46px);font-weight:500;letter-spacing:-.035em;
  line-height:1;font-variant-numeric:tabular-nums}
.tally span{display:block;margin-top:7px}
/* ---- the globe ----
 * The section carried a grid of its own for a while: the hero's lattice
 * continued down the page and deformed into a world map, and then a flat block
 * of accent with the world dotted into it. Both are gone. What stands here now
 * is the same land raster on a sphere: it turns slowly on its own, it can be
 * dragged to wherever you want it, and the four countries are lit.
 *
 * No box and no ground. The dots are the whole of it, on the page's own dark,
 * which is why there is nothing here to style but the shape of the space. */
/* pan-y and not none. On a phone the globe is the full width of the column,
   and touch-action:none over an area that size means a thumb swiped down the
   page spins the world instead of scrolling it. Vertical belongs to the page,
   horizontal to the globe. */
.globe{position:relative;aspect-ratio:4/3;touch-action:pan-y;cursor:grab}
.globe.dragging{cursor:grabbing}
.globe canvas{position:absolute;inset:0;width:100%;height:100%;display:block}
html:not(.js) .globe{display:none}

/* The premise sits closer than a full section pad at the bottom and further
   than it used to at the top, and each end has its own reason.

   ABOVE, the ticker used to be a rule across the page, so a short pad read as
   a gap under that rule rather than as the top of the next thing. The ticker is
   gone, and the hero now ends in nothing but its own scroll cue, so this pad is
   the only separation there is between a full screen of headline and the first
   paragraph. It carries the weight the rule used to.

   BELOW, it used to be the whole of the space before the catalogue, because
   the catalogue was a 420vh sticky rail with padding:0. The rail became a
   bento grid and quietly took the full section pad on top, so the two pads
   stacked: 217px of nothing between the premise and the catalogue on a 1440
   screen and 252 on a wide one, the widest joint on the page. The catalogue
   takes the shorter pad now (see #courses), and this one is unchanged. */
/* AND THE HERO IS NOT ABOVE IT ANY MORE. The workshops sit between the two
   now, so the top pad that carried the weight of the hero joint moved to
   #upcoming, and this one takes the shorter joint a run of sections takes. */
#statement{--pt:clamp(40px,5vw,80px);padding:var(--pt) 0 clamp(40px,4.4vw,72px)}
/* Even columns. The globe had the larger share, 1.25 to 1, while the headline
   beside it ran to four lines and was the taller of the two. Set on three lines
   the headline needs the width more than the globe does, and at 1 to 1 it sets
   at 50px on a wide screen where the old split would have left it 45. */
.saygrid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:clamp(28px,5vw,90px);
  align-items:start}
@media(max-width:900px){.saygrid{grid-template-columns:1fr}}
.sticksmall{position:sticky;top:120px}

/* ============================================================ the catalogue
 *
 * ONE ROW OF SEVEN PANELS, equal at rest, one opening under the hand. Before
 * it this was a bento grid,
 * and before that a sticky horizontal rail: 420vh of pinned frame showing
 * three of seven courses at a time, the other four behind a drag or a long
 * scroll, on a page whose whole first sentence is "seven courses". The grid
 * fixed that by showing all seven at once, each at the size seven share. The
 * row keeps the grid's rule, that every course is named on the screen at every
 * moment, and gives the one being looked at most of the width. It is an
 * expanding gallery, adapted from one Jad brought in on 14 Sep 2026.
 *
 * THE ONE PLACE ANYTHING MOVES ON HOVER. Nothing else on the site does, since
 * 14 Sep 2026, Jad's call, and since the same day this row is the exception,
 * also his call. Choosing between seven is the whole job of this section, and
 * a row that opens the course under the pointer while keeping the other six
 * named does that job better than seven small cards did. Every button, link
 * and card anywhere else still stays exactly where it is.
 *
 * Which panel is open is one attribute, data-open, and every rule below reads
 * it. No panel has it in the markup and base.js sets it.
 *
 * AT REST NOTHING IS OPEN, since 14 Sep 2026, Jad's call. The row used to
 * keep one course open at all times, the first until somebody chose another,
 * which put one course ahead of the other six before anybody had looked at
 * any of them. At rest the seven share the row and every cover is still; a
 * hand opens one, and when it leaves, the row goes back to seven.
 */
/* THE ARITHMETIC, all in one place. A closed panel grows by 1 and the open one
   by --grow, so the open one is --grow shares, out of --n - 1 + --grow, of the
   row less its gaps. --n comes from the renderer, which counts the courses.
   The caption is set at exactly that width whatever width its own panel has
   at the moment, and the cover is the largest square that fits the open panel
   above the caption, so neither of them ever resizes while a panel is moving.

   Six shares against one: the open panel takes half the row, which puts a
   428px cover in it at 1440, and a strip beside it is still wide enough, 105px
   there and 54 at the narrowest row, to hold its symbol and a slice of its
   drawing. At rest a panel is a seventh of the row, 181px at 1440.

   --rest and --strip are those two widths, and --sq, the element's square,
   is the strip less its margins, between 36px and 72: sized for the narrowest
   state a panel is ever in, so the square is one size at rest, beside an open
   panel and in the open panel, and never changes while a panel moves.

   --cap is the height reserved under the cover for the caption and its 10px
   margin: measured, not guessed, and set again wherever the caption changes
   shape. Measured, the caption is 93px with the button under the title, 99
   in the hand where the button is 44, and 76 to 80 with the button beside
   both lines, and each reserve is that and its margin with a pixel spare. */
/* "Seven courses designed for every discipline." sets on one line from about
   1000px up, and on a phone it broke before its last word and left
   "discipline." alone on the second line. Balanced, it breaks as two halves
   of a sentence instead. */
#courses .sechead h2{text-wrap:balance}
.shelf{--gap:8px;--grow:6;--h:clamp(440px,36vw,580px);
  container:shelf / inline-size;
  display:flex;gap:var(--gap);height:var(--h);margin-top:clamp(28px,3.4vw,54px)}
.shelf .tile{--cap:104px;
  --open:calc((100cqw - (var(--n) - 1) * var(--gap)) * var(--grow) / (var(--n) - 1 + var(--grow)));
  --side:min(var(--h) - var(--cap), var(--open));
  --rest:calc((100cqw - (var(--n) - 1) * var(--gap)) / var(--n));
  --strip:calc((100cqw - (var(--n) - 1) * var(--gap)) / (var(--n) - 1 + var(--grow)));
  --sq:clamp(36px, var(--strip) - 20px, 72px);
  flex:1 1 0;min-width:0;display:flex;text-align:left;
  /* flex-grow and not width, so the seven always add up to the row and
     nothing is ever pushed out of it. Every panel runs the same curve over the
     same time, so as one grows another shrinks by the same amount at every
     frame: the edge of a panel under a resting pointer only ever moves away
     from the pointer, and the row cannot open one panel by accident while
     opening another. */
  transition:flex-grow var(--t3) var(--ease),height var(--t3) var(--ease)}
/* The button is six pixels taller in the hand, and a caption that sets it
   under the title is taller with it. Before the wide layout below, which
   sets its own, because there the button stands beside two lines that are
   taller than it either way. */
@media (pointer: coarse){.shelf .tile{--cap:110px}}
.shelf .tile[data-open]{flex-grow:var(--grow)}
/* The reveal owns this element's transition as well, and a later rule with the
   same specificity would take the width's away, or give it the reveal's
   stagger, so the two are written out together here. */
.js .shelf .tile.up{transition:flex-grow var(--t3) var(--ease),height var(--t3) var(--ease),
  opacity var(--t3) var(--ease) calc(var(--i,0) * 85ms),
  transform var(--t3) var(--ease) calc(var(--i,0) * 85ms)}

/* A CARD WITH ITS OWN ACTIONS. The title is the link, stretched across the
   card so the whole drawing is still one click, and the button sits in the
   caption where it cannot be mistaken for part of the drawing. isolate, so
   the cover can sit under everything else in the card without going under the
   card's own ground. */
.tile .shot{position:relative;flex:1;min-width:0;overflow:hidden;isolation:isolate;
  border-radius:3px;background:var(--surface);border:1px solid var(--line);
  box-shadow:var(--card-shadow);display:flex;align-items:flex-end}
/* THE CANVAS IS SQUARE, and its size never follows the panel's width. Every
   scene draws in an S by S space, and covers.js builds the backing store from
   the element's width, once at load and again on a resize, so a canvas
   stretched by a moving panel would stretch its drawing: the foundations grid
   of circles came out as a grid of ellipses the one time a card went to 3:2.

   So it is sized from the row, through --side, and centred in its panel, which
   clips it. In a strip that shows a slice through the middle of the cover; as
   the panel opens the rest of the same drawing is uncovered around it, and
   nothing is ever redrawn at a new size. The canvas is transparent, so the
   card's ground either side of the square reads as one flat card with a
   drawing on it rather than a letterboxed picture.

   The caption is not laid over the drawing, because the foot of the frame is
   where several covers do their work: the landscape's ground line and its four
   season marks, the mechanical's floor. The square sits in the space above
   the caption, centred in it. */
.tile .shot canvas{position:absolute;z-index:-1;display:block;
  width:var(--side);height:var(--side);
  left:calc(50% - var(--side) / 2);top:calc(50% - (var(--side) + var(--cap)) / 2)}
/* THE ELEMENT, since 14 Sep 2026, Jad's call: each course set like an
   element in the periodic table, its number small in the corner of a square
   and its symbol large in the middle, Fn, Ad, Ri, with its short name under
   it. It replaced the title set up the spine of the strip, which was hard to
   read. The symbols are course facts and come from content/, so Jad changes
   one by editing a course file.

   The square is an ACID FIELD with ink on it: the identity's rule for the
   brand colour, a ground and never a line. At rest the row is seven of them,
   a strip of elements across the catalogue.

   TOP LEFT in every panel, and in the same place in every state, so nothing in
   it moves while the panels do. Not the foot, because the foot belongs to the
   caption when a panel opens, and to several covers' ground lines always. The
   name sits on a chip of the card's own ground, so it reads over whatever
   line of the cover crosses behind it.

   Its box is as wide as a panel at rest, whatever width its panel has now, for
   the same reason as the caption's: the name wraps once, at load, and never
   again while a panel moves. */
.tile .elem{position:absolute;left:10px;top:10px;z-index:2;pointer-events:none;
  width:calc(var(--rest) - 20px);display:flex;flex-direction:column;align-items:flex-start;gap:6px}
.tile .sq{position:relative;width:var(--sq);height:var(--sq);flex:none;border-radius:2px;
  background:var(--acid);color:var(--ink);display:grid;place-items:center;
  transition:background var(--t1),box-shadow var(--t1)}
/* the atomic number: where a real table puts it, small in the top left */
.tile .z{position:absolute;left:6px;top:4px;font-family:var(--mono);font-size:10px;
  letter-spacing:.04em;line-height:1.2}
/* Large and tight, and dropped a little below the middle, because the corner
   number sits above it and a symbol centred on the square read as rising
   into it. Sized off the square, so a smaller square in a narrow row keeps
   the same proportions rather than a symbol too big for it. */
.tile .sym{font-weight:500;font-size:calc(var(--sq) * .42);letter-spacing:-.04em;line-height:1;
  margin-top:calc(var(--sq) * .1)}
.tile .nm,.tile .when{padding:2px 5px;margin-left:-5px;background:var(--surface);border-radius:2px;
  transition:opacity var(--t2) var(--ease) 160ms}
.tile .nm{font-size:13px;font-weight:500;letter-spacing:-.01em;line-height:1.25;color:var(--ink)}
/* "From 2 Nov" or the format: the one fact in the panel's corner that is not
   a name, so it is the page's small mono rather than the name's grotesque. */
.tile .when{font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink3);white-space:nowrap}

/* BESIDE AN OPEN PANEL a strip has room for its square and not for words, so
   the name and the date go out at once and come back a beat after the row
   has returned to seven. The square stays: it is what names a strip. */
.shelf:has(.tile[data-open]) .tile:not([data-open]) :is(.nm,.when){opacity:0;transition:opacity var(--t1)}
/* IN THE OPEN PANEL the caption carries the course, and it is acid, so the
   square steps back to ink on the page's white with a hairline. Two acid
   blocks in one panel would be two things asking to be pressed. The square
   keeps its place, its size and its name; only its colours change, so the
   element reads as the same label it was at rest, now selected. Hiding the
   square outright was the other way to do it, and it cost the open course its
   symbol at the one moment somebody is reading it. */
.tile[data-open] .sq{background:var(--bg);box-shadow:inset 0 0 0 1px var(--line2)}

/* THE CAPTION IS ACID, since 14 Sep 2026, Jad's call: the open panel's caption
   is a field of #C8F44E with ink on it, so the course being looked at is the one
   acid thing in its panel (its own square turns white while it is open). That
   is the identity's rule for the brand colour, a GROUND with ink on it and
   never ink itself, and it is the page's rule for the accent too, the thing
   you are about to act on. It was the page's white with a hairline, a label
   laid on the card.

   The acid stops at the caption. Several covers fill parts of their own frame
   with the paper colour to hide what is behind a shape (--c-back in covers.js),
   so an acid panel behind a cover would come out as acid with paper patches in
   it. The card stays the grey surface and the drawing stays on it.

   IT NEVER REFLOWS. It is set at the open panel's width in every panel, open
   or not, and the panel clips it, so as a panel grows the caption is uncovered
   rather than re-wrapped, and nothing in it moves while the width does. It
   goes out at once when a panel closes and comes in a beat after one opens,
   once most of the width has arrived.

   Hidden by opacity alone, never by visibility or display. A closed panel's
   title link is still in the page for a screen reader and still in the tab
   order for a keyboard, and focusing it is what opens its panel (base.js), so
   the next course is never a link that cannot be reached. With a script
   running, a closed caption takes no pointer, so a tap on a strip lands on the
   panel, which opens it, rather than on a link nobody could see. With no
   script it keeps the pointer, and a strip is simply a link to its course. */
.tile .cap{flex:none;width:calc(var(--open) - 20px);margin:0 10px 10px;
  padding:12px 14px 11px;background:var(--acid);border:1px solid var(--acid);border-radius:2px;color:var(--ink);
  display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:12px;row-gap:5px;align-items:center;
  transition:opacity var(--t2) var(--ease) 160ms}
.tile:not([data-open]) .cap{opacity:0;transition:opacity var(--t1)}
.js .tile:not([data-open]) .cap{pointer-events:none}
/* The title across the top, and the details and the button on the line under
   it. One layout at every width, where there used to be four chosen by the
   card's width: the caption has a single width now, so it has a single shape,
   and nowrap is the guarantee that a title is one line and never two. */
.tile .cap h3{grid-column:1 / -1;grid-row:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tile .cap .cmeta{grid-column:1;grid-row:2}
.tile .cap .acts{grid-column:2;grid-row:2}
/* AND WHERE THE OPEN PANEL IS WIDE, the button beside both lines, which is two
   lines of caption rather than three and gives the height back to the cover.
   The open panel is half the row less its gaps, so a row of 1100 makes a
   caption of 506, whose text column is left with 319px beside a 141px
   "Book a session": the longest title, "Grasshopper for Interior Design",
   needs about 235 of it at the 16px it is set at there. */
@container shelf (min-width:1100px){
  .shelf .tile{--cap:90px}
  .tile .cap{column-gap:16px}
  .tile .cap h3{grid-column:1}
  .tile .cap .acts{grid-row:1 / span 2}
}
.tile h3{margin:0;font-size:clamp(16px,1.3vw,21px);font-weight:500;letter-spacing:-.02em}
/* The details are ink like the rest of the caption: the page's quiet grey is
   measured against white, and on acid it goes muddy. Mono and capitals already
   say they are the small print. */
.tile .cap .cmeta{color:var(--ink)}
/* UNDERLINED UNDER THE POINTER. The title used to turn acid to say the card
   was under the pointer, and on an acid caption that is a title in the
   colour of its own ground. An ink underline says the same and moves nothing.
   The panel opening is the other half of that, and the only half that moves. */
.tile h3 a{text-decoration:underline;text-decoration-color:transparent;text-decoration-thickness:1px;
  text-underline-offset:4px;transition:text-decoration-color var(--t1)}
@media(hover:hover){.tile .shot:hover h3 a{text-decoration-color:var(--ink)}}
/* the title link, stretched over the whole card */
.tlink{color:inherit}
.tlink::after{content:'';position:absolute;inset:0;z-index:1}
.tlink:focus-visible{outline:none}
/* The ring is round the card, on the page's white, where the accent's ink
   colour reads; inside the caption, on acid, it would not, so a focused
   button there is ringed in ink. */
.tile .shot:has(.tlink:focus-visible){outline:2px solid var(--acid-ink);outline-offset:3px}
.tile .cap :focus-visible{outline-color:var(--ink)}

/* THE ACTION LIVES IN THE CAPTION, beside the title and the details, so each
   course is one object: what it is, how long, and the one thing to do. Two
   kinds, and they look different on purpose. A course with dates is booked by
   a call and a recorded one is bought, and theirs is the solid button; the ones
   taught on demand are booked by mail, so theirs is the outlined one, a
   conversation rather than a checkout.
   z-index 2 because the title link is stretched over the whole card, and
   without it the button would sit underneath that link and never be clicked. */
.tile .acts{position:relative;z-index:2;display:flex}
.tile .acts .btn{display:inline-flex;align-items:center;min-height:38px;padding:0 14px;font-size:11px}
@media (pointer: coarse){.tile .acts .btn{min-height:44px}}
/* ON THE ACID CAPTION the solid button is INK with acid type, since an acid
   button on an acid field is no button at all, and the outlined one is an ink
   line, which is ink on acid and not the acid hairline on paper the identity
   rules out. Under the pointer the outlined one fills with ink, and the solid
   one lifts to the deep block's line colour, so both still answer a hover by
   colour alone. */
.tile .cap .btn{background:var(--ink);color:var(--acid);border:1px solid var(--ink)}
.tile .cap .btn.ghost{background:transparent;color:var(--ink);border-color:var(--ink)}
@media(hover:hover){
  .tile .cap .btn:hover{background:var(--deep-line);border-color:var(--deep-line);color:var(--acid)}
  .tile .cap .btn.ghost:hover{background:var(--ink);border-color:var(--ink);color:var(--acid)}
}

/* THE LABEL AND THE PRICE, SIDE BY SIDE. The price used to roll up into the
   button on hover, the label going up and out as it came in. Nothing in a
   button moves on hover since 14 Sep 2026, Jad's call, so it carries both at
   once, everywhere, which is what it already did on a phone. */
.buy .roll{display:flex;gap:.55em;line-height:1.25}
.buy .roll > span + span::before{content:'\00b7';margin-right:.55em}

/* SAID ONCE, at the top of the catalogue, instead of beside every online
   course, where seven cards carried the same sentence three times. */
.inperson{margin:14px 0 0;font-size:15px;color:var(--ink2)}
.inperson a{color:var(--ink);text-decoration:underline;text-decoration-color:var(--acid-ink);
  text-underline-offset:3px;transition:color var(--t1)}
@media(hover:hover){.inperson a:hover{color:var(--acid-ink)}}
@media (pointer: coarse){.inperson a{display:inline-block;padding:12px 0}}
/* No gap between wrapped lines. In the hand, "Intermediate" and the hours need
   a pixel or two more than the 147px a phone's caption leaves them beside the
   button, so they wrap, and they took the 12px gap down with them: measured,
   those captions came out 104px to the others' 99. Two lines with no gap are
   still shorter than the button, so every caption keeps one height and the
   reserve above keeps meaning what it says. */
.tile .cmeta{display:flex;gap:0 12px;flex-wrap:wrap}

/* A STACK BELOW 760, and the same row turned on its side rather than seven
   full cards. Below 760 a row of seven leaves the open panel too narrow for
   its caption, so the panels stack, and they stack as the same accordion
   because a column of seven square covers is five phone screens of catalogue,
   where this is about one and a half, and every course is on it at once, its
   element and its name side by side, set level.

   The width does not move here; the height does. That is what makes the
   square cover easy in a column: it is sized from the column's width, which
   no panel changes, and the open panel is exactly that square plus its
   caption. A closed panel is 72px of the same card: the element at the left,
   the name and the date beside it, and a band through the cover behind. At
   rest every band is closed, as every panel of the row is. Opening one moves
   the cover down to its place with the height and never resizes it. base.js
   holds the tapped panel still on the screen while the one above it closes,
   which a stack needs and a row does not. */
@media (max-width:759.98px){
  .shelf{flex-direction:column;height:auto}
  .shelf .tile{--side:min(100cqw, 440px);--sq:52px;flex:none;height:72px}
  .shelf .tile[data-open]{height:calc(var(--side) + var(--cap))}
  /* hung from the foot of the square rather than the foot of the panel, so
     it is uncovered as the panel grows rather than carried down by it */
  .tile .shot{align-items:flex-start}
  .tile .cap{width:calc(100cqw - 20px);margin-top:var(--side)}
  /* the square at the left and the name beside it, the date under the name */
  .tile .elem{width:calc(100cqw - 20px);height:var(--sq);display:grid;
    grid-template-columns:var(--sq) minmax(0,1fr);column-gap:12px;row-gap:4px;align-content:center}
  .tile .sq{grid-row:1 / span 2}
  .tile .nm{font-size:15px;align-self:end;justify-self:start}
  .tile .when{align-self:start;justify-self:start}
  /* A band is the full width whether another is open or not, so its words
     stay. */
  .shelf:has(.tile[data-open]) .tile:not([data-open]) :is(.nm,.when){opacity:1}
}

/* Under reduced motion a panel is simply open or closed, at once, and the
   caption and the element's words swap without a fade. */
@media(prefers-reduced-motion:reduce){
  .shelf .tile,.js .shelf .tile.up{transition:none}
  .shelf .tile .cap,.shelf .tile .sq,.shelf .tile .nm,.shelf .tile .when,
  .shelf:has(.tile[data-open]) .tile:not([data-open]) :is(.nm,.when){transition:none}
}

/* ============================================================ the scripts
 *
 * A course card is a drawing and a script card is a file, and they are built
 * differently on purpose. The seven covers are canvases because a course is
 * geometry you are going to learn to make. A script is a thing you download,
 * so its card is shaped like the file it is: an extension, a name, a line
 * about what it does, and what it costs. Giving scripts drawn covers as well
 * would have made the two shelves read as one long shelf of the same product,
 * which is exactly the thing that makes a teacher's site look like a store.
 */
.sechead{display:flex;align-items:flex-end;justify-content:space-between;
  gap:clamp(18px,3vw,54px);flex-wrap:wrap}
.sechead .mono{max-width:30ch;margin:0}

.toolgrid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(12px,1.4vw,20px);margin-top:clamp(28px,3.4vw,54px)}
@media(max-width:980px){.toolgrid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:560px){.toolgrid{grid-template-columns:1fr}}

.tool{display:flex;flex-direction:column;gap:9px;
  background:var(--surface);border:1px solid var(--line);border-radius:3px;
  padding:clamp(16px,1.5vw,22px);
  transition:border-color var(--t2)}
/* The border darkens, and the card stays where it is. NOTHING MOVES ON HOVER, since 14 Sep 2026: Jad's call, for the whole site but the catalogue's row. */
@media(hover:hover){.tool:hover{border-color:var(--line2)}}
.toolbar{display:flex;align-items:center;justify-content:space-between;gap:10px}
/* The extension is the closest thing a file has to a face, and it is the one
   piece of metadata that tells an architect in one glance whether this drops
   onto a canvas, into a component tray, or into the plugin folder. */
.ext{font-family:var(--mono);font-size:11px;letter-spacing:.1em;color:var(--ink2);
  border:1px solid var(--line2);border-radius:var(--r-ctl);padding:4px 9px}
/* Free and paid share ONE slot, one size and one pill, and differ only by the
   word inside them. A second treatment for the free ones would be the page
   apologising for them, and a strikethrough or a "was" would make this a shop.
   The pill is the accent's TINT and not the accent: nothing on a teaser card
   is a live value, because there is nothing here you can act on. The value
   arrives on the page where the button is. */
.price{font-family:var(--mono);font-size:11px;letter-spacing:.06em;white-space:nowrap;
  background:var(--amber-bg);color:var(--amber);padding:4px 10px;border-radius:var(--r-ctl)}
.tool h3{margin:5px 0 0;font-size:clamp(16px,1.25vw,20px);font-weight:500;letter-spacing:-.02em}
.tool .tsay{margin:0;color:var(--ink2);font-size:clamp(13px,1.02vw,15px);line-height:1.55}
/* margin-top:auto pins the meta to the bottom, so a three-line description and
   a one-line description still line their meta rows up across the grid. */
/* A separator, because these two values run together without one. The course
   cards get away with a plain gap because their values are one word each;
   "DOCUMENTATION" beside "RHINO 8, 9" reads as a single string. */
.tool .cmeta{display:flex;gap:0;flex-wrap:wrap;margin-top:auto;padding-top:10px}
.tool .cmeta span{color:var(--ink3)}
.tool .cmeta span+span::before{content:'·';margin:0 9px;color:var(--line2)}
.toolmore{margin:clamp(26px,3vw,44px) 0 0;display:flex;gap:12px;flex-wrap:wrap;align-items:center}

/* ============================================================ what you leave with */
.deliver{border-top:1px solid var(--line)}
.drow{display:grid;grid-template-columns:80px minmax(0,1fr) minmax(0,1.1fr);
  gap:clamp(14px,2.4vw,44px);align-items:baseline;
  padding:clamp(20px,2.6vw,38px) 0;border-bottom:1px solid var(--line);
  position:relative;transition:color var(--t2)}
@media(max-width:760px){.drow{grid-template-columns:56px minmax(0,1fr)}
  .drow .dsay{grid-column:2}}
/* The row lights from the left as the pointer crosses it: a wash, not a
   fill, so the text on top of it never loses contrast. */
.drow::before{content:'';position:absolute;inset:0;z-index:-1;
  background:linear-gradient(90deg,color-mix(in srgb,var(--acid) 7%,transparent),transparent 62%);
  opacity:0;transition:opacity var(--t2)}
@media(hover:hover){.drow:hover::before{opacity:1}}
@media(hover:hover){.drow:hover .dnum{color:var(--acid-ink)}}
.dnum{font-family:var(--mono);font-size:11px;color:var(--ink3);transition:color var(--t2)}
.dttl{font-size:clamp(19px,1.9vw,30px);font-weight:500;letter-spacing:-.022em;margin:0}

/* ============================================================ the reel
 *
 * Section 03 is in two halves. The first is the list of things a course ends
 * in; the second is that list being drawn - the live site's hero, ported whole,
 * cycling one technique at a time. A list of deliverables is a claim, and the
 * reel is the only part of this page that shows the claim being carried out,
 * which is why it sits directly under the list rather than in a section of its
 * own with a number of its own.
 */
/* Side by side and evenly split: the list is the claim and the reel is the
   claim being carried out, and putting one under the other made the second
   read as an afterthought to the first rather than as its evidence. Two equal
   columns, because neither is subordinate.
   They stack below 1000px - not 900, which is where the rest of the page
   turns: half of 900 is a 450px column, and the reel needs more than that
   before its own narrow layout takes over and the drawing shrinks to a
   thumbnail. */
.dsplit{display:grid;grid-template-columns:1fr 1fr;gap:clamp(24px,3vw,52px)}
/* The two halves are the same height, and the taller one sets it. Grid already
   stretches its items; what it does not do is pass that height down to the
   frame, which is a grandchild - so each half is a flex column and the frame
   is the part that grows. The list is the taller of the two at every width
   this layout is used at, so in practice the reel is sized by the list beside
   it, which is the point: two panels of different heights read as one thing
   next to a smaller thing, not as two halves of a claim. */
.dhalf{display:flex;flex-direction:column;min-width:0}
@media(max-width:1000px){.dsplit{grid-template-columns:1fr;gap:clamp(40px,5vw,76px)}}

/* At half width the deliverable rows cannot stay a three-column table - the
   description column lands at about 26 characters and every line breaks twice.
   This is the same shape the narrow-viewport rule already gives them, applied
   on the column's width rather than the window's, because at 1500px wide with
   two columns the window rule never fires. */
@media(min-width:1001px){
  .dsplit .drow{grid-template-columns:52px minmax(0,1fr);gap:14px;
    padding:clamp(16px,1.5vw,24px) 0}
  .dsplit .drow .dsay{grid-column:2;margin-top:6px}
  .dsplit .dttl{font-size:clamp(18px,1.35vw,23px)}
}
.dlabel{display:flex;align-items:baseline;gap:14px;margin:0 0 clamp(16px,2vw,26px);
  font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink3)}
.dlabel b{color:var(--acid-ink);font-weight:400}
.dlabel span{flex:1;height:1px;background:var(--line)}

/* The frame is 16/9 and floored, because the reel reserves a fixed strip at
   the bottom for the definition and a fixed gap at the top: below about 480px
   of height those two eat the drawing rather than the margin, and the geometry
   comes out postage-stamp sized inside a wide empty box. */
/* Height, not aspect-ratio. aspect-ratio with a max-height shrinks the BOX,
   not just its height - the ratio is preserved, so capping the height pulled
   the frame in off both margins and it stopped reaching the measure. The
   drawing sizes itself off whatever box it is given, so the box is simply told
   how tall to be and left full width.
   The ground is --bg and not --bg2 because the reel fades its drawing out at
   the edges against --page, which is bound to --bg: fade to one colour on top
   of another and the vignette leaves a visible pane instead of dissolving. */
/* A floor, not a height. It grows to whatever the column beside it is, and
   the floor is what carries it when the two stack and there is nothing to
   match. The drawing sizes itself off the box it is given, so it simply gets
   taller with it. */
.reelframe{position:relative;border:1px solid var(--line);border-radius:3px;
  background:var(--bg);overflow:hidden;flex:1;
  min-height:clamp(460px,42vw,640px)}
#reelCanvas{position:absolute;inset:0;width:100%;height:100%;display:block;
  opacity:0;transition:opacity var(--t3) var(--ease)}
#reelCanvas.on{opacity:1}
/* With no script the canvas never gets .on and never draws, so the frame would
   stand there as an empty ruled box. It is removed instead. */
html:not(.js) .reelframe{display:none}

/* The scene names itself under the frame, typed a letter at a time by the reel
   itself - the same code path that drives the headline on the live site, which
   is why the caret is here rather than being a decoration. */
/* Top left, not bottom. The reel draws its own definition strip along the
   bottom edge and its credit line just above it, so a caption there lands on
   top of both - and the drawing reserves a gap at the top for exactly this,
   because on the live site the scene name is the headline. */
.reelword{position:absolute;left:0;right:0;top:0;margin:0;
  padding:18px var(--gut) 0;display:flex;align-items:center;gap:3px;
  font-family:var(--mono);font-size:12px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink2);pointer-events:none}
.reelcaret{width:7px;height:13px;background:var(--acid);display:inline-block;
  opacity:0;transform:translateY(1px)}
.reelcaret.typing{opacity:1;animation:reelblink 1s steps(1) infinite}
@keyframes reelblink{0%,50%{opacity:1}50.01%,100%{opacity:0}}
@media(prefers-reduced-motion:reduce){.reelcaret{display:none}}
@media(max-width:640px){.reelframe{aspect-ratio:4/5;min-height:420px}}

/* ============================================================ the person */
/* The two halves are the same height and the picture is what gives, because
   the words cannot: a column of type has the height its sentences need, and a
   photograph can be cropped to whatever is left. align-items:center instead
   left the picture floating in the middle of a taller column with air above
   and below it, which reads as two things placed near each other rather than
   as one row. */
.who{display:grid;grid-template-columns:minmax(0,.85fr) minmax(0,1fr);
  gap:clamp(28px,5vw,90px);align-items:stretch}
@media(max-width:900px){.who{grid-template-columns:1fr}
  /* stacked, there is no column beside it to match, so it takes its own shape
     back rather than growing to whatever the paragraphs happen to need */
  .who .portrait{height:auto;min-height:0;aspect-ratio:1/1}}
/* Square, because the photograph is. Cropping it to 4:5 would take the slide
   out of the frame, and the slide is half of why the picture reads as teaching
   rather than as a portrait. */
.portrait{background:var(--surface);border:1px solid var(--line);
  position:relative;overflow:hidden;border-radius:3px;height:100%;min-height:100%}
.portrait img{width:100%;height:100%;object-fit:cover;display:block}

.portrait figcaption{position:absolute;left:14px;bottom:12px;z-index:2}

/* ============================================================ help with a project
 *
 * A band with the offer on the left and its terms on the right: the shape the
 * reference for this section uses, and it works because the two halves are
 * doing different jobs - one asks for the click, the other answers the two
 * questions anybody would ask before making it.
 *
 * ONE THING IS DELIBERATELY NOT COPIED. The reference puts its accent on a
 * word of the headline. Here the accent means the live value or the thing you
 * are meant to press, and on this band that is the button - so the headline is
 * ink like every other headline on the page, and the rules beside the specs
 * are structural grey. An accent on a headline word AND on the button would be
 * two live values in one band, which is one too many for a rule this strict.
 */
.train{display:grid;grid-template-columns:minmax(0,1.05fr) minmax(0,1fr);
  gap:clamp(32px,6vw,110px);align-items:start}
@media(max-width:900px){.train{grid-template-columns:1fr;gap:clamp(28px,5vw,44px)}}
.train .tsay{color:var(--ink2);margin:0 0 clamp(26px,3.4vw,44px);max-width:46ch}
/* The email is the second way in, so it is a link in the sentence rather than
   a second button. On the deep block --acid-ink is the full acid. */
.train .tsay a{color:var(--ink);text-decoration:underline;text-decoration-color:var(--acid-ink);
  text-underline-offset:3px;transition:color var(--t1)}
@media(hover:hover){.train .tsay a:hover{color:var(--acid-ink)}}
/* Two terms, each a label over an answer, divided by a rule that starts at the
   label and stops at the baseline of the answer - the divider is the height of
   the thing it divides, not the height of the row. */
.terms{display:flex;gap:clamp(22px,3.6vw,54px);flex-wrap:wrap}
.term{padding-left:clamp(14px,1.6vw,22px);border-left:1px solid var(--line2)}
.term span{display:block;font-family:var(--mono);font-size:11.5px;letter-spacing:.02em;
  color:var(--ink3);margin-bottom:10px}
.term b{display:block;font-size:clamp(20px,2.2vw,30px);font-weight:500;
  letter-spacing:-.024em;color:var(--ink)}

/* ============================================================ questions
 *
 * Two columns: what this is on the left, the questions on the right. The
 * layout is the premise section's and the who section's, which is the point -
 * it is the shape this page uses whenever something needs a heading held
 * still beside a list, and the answers section had been the one place that
 * ignored it and ran full-bleed like a support site.
 *
 * Each question carries a number in the mono, the way the deliverables in
 * section 03 do. That is not decoration: it is the same device saying the same
 * thing, that these are items in a set and you can count them.
 *
 * They are still <details>. The accordion is the right control and it works
 * with no script at all - what changed is everything around it.
 */
.qgrid{display:grid;grid-template-columns:minmax(0,.62fr) minmax(0,1fr);
  gap:clamp(28px,5vw,96px);align-items:start}
@media(max-width:900px){.qgrid{grid-template-columns:1fr;gap:clamp(24px,4vw,40px)}}
/* held beside the list rather than scrolling away from it */
.qaside{position:sticky;top:120px}
@media(max-width:900px){.qaside{position:static}}
.qaside h2{font-size:clamp(26px,3.2vw,44px);font-weight:500;letter-spacing:-.03em;margin:0}
.qaside p{color:var(--ink2);margin:18px 0 0;max-width:34ch}
/* .btn is an <a>, so it is inline, and every other one on this page happens to
   sit in a flex container which makes it a flex item and gives its margins
   effect. This one is a plain block child - left inline, the top margin was
   dropped on the floor and the button sat on top of the paragraph above it. */
.qaside .btn{display:inline-flex;align-items:center;margin-top:clamp(22px,2.6vw,34px)}

.q{border-top:1px solid var(--line)}
.q:last-of-type{border-bottom:1px solid var(--line)}
.q summary{list-style:none;cursor:pointer;padding:clamp(17px,1.9vw,24px) 0;
  display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:clamp(14px,1.6vw,22px);
  align-items:baseline;
  font-size:clamp(16px,1.42vw,21px);font-weight:500;letter-spacing:-.018em;
  transition:color var(--t1)}
.q summary::-webkit-details-marker{display:none}
@media(hover:hover){.q summary:hover{color:var(--acid-ink)}}
.q summary::before{content:'/' counter(faq, decimal-leading-zero);
  font-family:var(--mono);font-size:11px;letter-spacing:.06em;color:var(--ink3);
  transition:color var(--t1)}
.qgrid{counter-reset:faq}
.q{counter-increment:faq}
.q[open] summary::before{color:var(--acid-ink)}
@media(hover:hover){.q summary:hover::before{color:var(--acid-ink)}}
/* a plus that becomes a minus - the one control everybody already reads */
.q .qi{width:13px;height:13px;position:relative;flex:0 0 auto;align-self:center}
.q .qi::before,.q .qi::after{content:'';position:absolute;inset:50% 0 auto 0;height:1px;
  background:currentColor;transition:transform var(--t2) var(--ease)}
.q .qi::after{transform:rotate(90deg)}
.q[open] .qi::after{transform:rotate(0)}
.q .qa{margin:0 0 0 calc(11px + clamp(14px,1.6vw,22px));
  padding:0 0 clamp(18px,2.2vw,28px);max-width:60ch;color:var(--ink2)}
@supports (interpolate-size:allow-keywords){
  :root{interpolate-size:allow-keywords}
  .q::details-content{block-size:0;overflow:hidden;
    transition:block-size var(--t2) var(--ease),content-visibility var(--t2);
    transition-behavior:allow-discrete}
  .q[open]::details-content{block-size:auto}
}

/* ONE CONTROL IN TWO PARTS. The field and the button share a border and a
   ground, so the pair reads as a single thing to fill in rather than as a box
   standing next to a button. The border lives on the form and the field brings
   none of its own, which is also what keeps the two ends aligned at every
   width without either being told a size.

   The button keeps the accent, because it is the one action in the hero. It is
   a check rather than a sentence: at 350px the words were most of the
   control's width, and what a button at the end of an email field does is not
   in doubt. The words move to the placeholder, where they are the invitation
   rather than a label, and the field keeps its aria-label so the accessible
   name is still the honest one. */
.form{display:flex;align-items:center;gap:6px;max-width:620px;
  background:var(--surface);border:1px solid var(--line2);
  border-radius:var(--r-ctl);padding:5px;
  transition:border-color var(--t1),background var(--t1)}
.form:focus-within{border-color:var(--acid-ink);background:var(--surface2)}
.form input{flex:1 1 auto;min-width:0;background:transparent;border:0;color:var(--ink);
  font:inherit;font-size:16px;height:44px;padding:0 12px}
.form input:focus{outline:none}
.form input::placeholder{color:var(--ink3)}
.form .btn{flex:0 0 auto;width:44px;height:44px;padding:0;
  border-radius:calc(var(--r-ctl) - 3px);display:grid;place-items:center}

/* THE BUTTON IS A CHECK, AND PRESSING IT SENDS THE CHECK. It lifts out of the
   button on the diagonal a stamp travels, which is how a message leaves, and
   the one that comes back draws itself stroke by stroke instead of appearing,
   so the confirmation is something you watch happen rather than something you
   find already done. overflow:visible on the svg, because the tick leaves its
   own box on the way out. */
.tick{width:20px;height:20px;display:block;overflow:visible}
.tick path{stroke-dasharray:30;stroke-dashoffset:0}
.form.sending .tick{animation:send 700ms var(--ease-io) both}
.form.sending .tick path{animation:draw 700ms var(--ease-io) both}
@keyframes send{
  0%  {transform:translate(0,0);opacity:1}
  36% {transform:translate(24px,-24px);opacity:0}
  37% {transform:translate(-18px,18px);opacity:0}
  60% {transform:translate(-18px,18px);opacity:1}
  100%{transform:translate(0,0);opacity:1}
}
@keyframes draw{
  0%,36%  {stroke-dashoffset:0}
  37%,60% {stroke-dashoffset:30}
  100%    {stroke-dashoffset:0}
}
/* Once it is sent the control is spent: the field has been emptied and there is
   a line under it saying so, and a live button there only invites a second go. */
.form.done{opacity:.6;pointer-events:none}

.btn{position:relative;overflow:hidden;background:var(--acid);color:#0A0B0D;
  font-family:var(--mono);font-size:12px;letter-spacing:.12em;text-transform:uppercase;
  padding:18px 30px;border-radius:var(--r-ctl);white-space:nowrap;
  transition:transform var(--t2) var(--ease),background var(--t1)}
/* The small give on a PRESS stays: it is the answer to a click, not a hover.
   The drift toward the pointer is gone. NOTHING MOVES ON HOVER, since 14 Sep 2026: Jad's call, for the whole site but the catalogue's row. */
.btn:active{transform:scale(.97)}
@media(hover:hover){.btn:hover{background:#D8FF63}}
.btn.ghost{background:transparent;color:var(--ink);border:1px solid var(--line2)}
@media(hover:hover){.btn.ghost:hover{border-color:var(--acid-ink);color:var(--acid-ink);background:transparent}}
.sent{display:none;margin:16px 0 0;font-family:var(--mono);font-size:12px;color:var(--acid-ink)}
.sent.on{display:block;animation:sin var(--t2) var(--ease) both}
@keyframes sin{from{opacity:0;transform:translateY(6px)}}

/* ============================================================ the foot
 *
 * A band of its own, in the page's surface rather than its ground, so the site
 * ends on something rather than trailing off. It is the only place on the page
 * that changes ground, and that is what makes it read as the end.
 *
 * THE WORDMARK IS GONE. It was `jad.computes` set across the full measure at
 * 6em, and even sized to fit exactly it was the wrong idea: a 225px word is the
 * loudest thing on a page whose whole manner is quiet, it repeats what the
 * header already says, and it says it at the moment a visitor is leaving. The
 * mark is back to the size it is everywhere else, at the top of a column, with
 * the sentence the site opens with underneath it.
 */
/* The questions section carried a full section pad underneath it and the
   footer another one on top, so the last answer and the first footer link had
   most of a screen of nothing between them. The footer now has its own ground
   and a rule to arrive on, which is what separates it - the empty band was
   doing a job that a change of colour does better. */
#questions{padding-bottom:clamp(36px,4vw,64px)}
/* AND IT HAD NO PAD ON TOP AT ALL, an inline padding-top:0 from when the
   section above was the same ground and its own bottom pad was the joint. Who
   teaches it is a band now, and the band's edge is where this section starts, so
   the eyebrow sat 8px under a change of colour. It takes the same shorter pad
   the two banded sections take at their own top edge. */
#questions{--pt:clamp(56px,7vw,110px)}
/* THE SURFACE GREY, with a hairline to arrive on. It was acid for a day, the
   brand colour as the last thing on the page, and went back to this on 14 Sep
   2026, Jad's call. */
.foot{background:var(--surface);border-top:1px solid var(--line);
  padding:clamp(44px,5.5vw,88px) 0 clamp(22px,2.4vw,34px);
  margin-top:clamp(48px,7vw,120px)}
/* Brand column, then the links. One grid, so the columns line up with each
   other rather than being pushed to the ends of a flex row. */
.footgrid{display:grid;grid-template-columns:minmax(0,1.15fr) repeat(3,minmax(0,.62fr));
  gap:clamp(24px,3.4vw,56px);align-items:start}
@media(max-width:980px){.footgrid{grid-template-columns:repeat(3,minmax(0,1fr))}
  .footbrand{grid-column:1 / -1;margin-bottom:clamp(18px,2.4vw,30px)}}
/* THREE LISTS OF FOUR, THREE AND THREE DO NOT FIT TWO COLUMNS. Stacking the
   two short ones down the right left the left column ending 152px above the
   right, which is a hole in the bottom corner of the page and the reason the
   footer read as unfinished. The two lists that nearly balance sit side by
   side; the third spans the full width underneath and lays its links out as a wrapping
   row rather than a column: measured, that is 134px against 180, because two
   of the three fit on one line once they are not in a 165px cell. */
@media(max-width:560px){.footgrid{grid-template-columns:repeat(2,minmax(0,1fr));
    gap:clamp(20px,4vw,30px) clamp(20px,3.4vw,40px)}
  .footbrand{grid-column:1 / -1}
  .footgrid > div:nth-child(4){grid-column:1 / -1}
  .footgrid > div:nth-child(4) ul{display:flex;flex-wrap:wrap;gap:0 clamp(18px,5vw,28px)}
  .foot{margin-top:clamp(28px,5vw,48px);padding-top:clamp(32px,5vw,56px)}}
.footbrand .mark{font-size:19px}
/* 48ch holds the one sentence on one line where the column has the room; at
   the old 34ch, sized for two sentences, it left "designers." on its own. */
.footsay{color:var(--ink2);font-size:14px;line-height:1.55;margin:16px 0 0;max-width:48ch}
.footgrid ul{list-style:none;margin:9px 0 0;padding:0;display:grid;gap:8px}
.footgrid a{color:var(--ink2);font-size:14px;transition:color var(--t1)}
@media(hover:hover){.footgrid a:hover{color:var(--acid-ink)}}

/* ============================================================ reveals
 *
 * One utility, driven by an observer rather than by a view() timeline. The
 * timeline version is measured in the element's own travel through the
 * viewport, and anything near the foot of a document never finishes that
 * travel: the page runs out of scroll first and the last block stays half
 * faded for good. An observer fires a handful of times in a session and a
 * transition always reaches its end.
 *
 * The hidden state lives behind .js, set by one line before the stylesheet.
 * With no script, nothing is hidden and the page is simply complete.
 */
.js .up{opacity:0;transform:translateY(34px);
  transition:opacity var(--t3) var(--ease),transform var(--t3) var(--ease);
  transition-delay:calc(var(--i,0) * 85ms)}
.js .up.in{opacity:1;transform:none}

@media(prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  .js .up{opacity:1;transform:none;transition:none}
  .cue u::after{animation:none}
  .line > span{transform:none}
  #cur,#curdot{display:none}
  .btn{transform:none}
  /* The reel fades itself in by adding .on, and under reduced motion the code
     path that adds it is the one that is not taken. The class is added there
     now; this makes the arrival instant rather than a fade. */
  #reelCanvas{transition:none}
  }
/* ============================================================ on a phone
 *
 * Three things a desktop layout gets wrong on a handset, and none of them show
 * up in a screenshot.
 *
 * A THUMB IS NOT A CURSOR. A pointer hits a 1px target; a thumb needs about
 * 44px, and measured against that this page had a 30px theme switch, a 26px
 * mark, a 17px scroll cue and fourteen footer links 18px tall. The fix is
 * padding rather than size - the type stays where it is and the box around it
 * grows - and it is scoped to `pointer: coarse` so a mouse still gets the
 * tight, quiet header the page was designed with.
 *
 * ELEVEN-PIXEL MONO IS A DESKTOP SIZE. It is a label seen at arm's length on a
 * large screen; held at reading distance on a phone it is a smudge. Everything
 * that small comes up a point.
 *
 * AND THE STATS DID NOT FIT. Four figures in a flex row wrapped three-and-one,
 * which reads as a mistake rather than as a row.
 */
@media (pointer: coarse) {
  /* THE BAR WAS 71.6px TALL AND THE PAGE BEHIND IT IS 667px. That is a tenth of
     the screen spent on a wordmark and two controls, and it was the reason the
     headline was printed through it. The targets stay at 44px; what goes is the
     air around them, and the two round controls give their spare height back
     with a negative margin, so the box a thumb hits is still 44 while the row
     it sits in is 32. Measured after: 49.3px, and it no longer GROWS when you
     scroll - the scrolled rule's 12px used to beat the coarse rule's 12px on
     specificity and win a bar that was taller in the state meant to be shorter.
     Both states are named here for that reason. */
  /* AND 44.6 AFTER A SECOND PASS. The height a control needs to be
     TOUCHED and the height it needs to LOOK right are different numbers, and
     the bar was paying the larger one twice: once in its own height, and again
     in the size of the two pills, which is what made it read as heavy rather
     than merely tall. Every control keeps a 44px hit area and gets a smaller
     visible box, the difference handed back to the row as a negative margin
     or, for the disc, as an invisible pad. The row is 34 and the bar is 44.6. */
  /* MORE AIR ABOVE THAN BELOW, because what is crowding the bar is above it.
     The browser's own address bar sits directly on top of this one and the
     wordmark started 13px under it, so the two read as one stacked strip of
     chrome. The row is still 34 and the extra is padding, not height in the
     controls. env() is the safe-area inset, which is zero in Chrome on Android
     because the browser chrome is outside the viewport there, and is the notch
     on an iPhone in standalone mode, where it is the difference between a
     wordmark and a wordmark under a sensor housing. */
  .bar,body.scrolled .bar{padding-top:calc(16px + env(safe-area-inset-top));padding-bottom:6px}
  /* AND IT HAS TO COVER WHAT PASSES UNDER IT. 72% of the ground is enough on a
     desktop, where the bar is 1300px of mostly empty width and the odd line of
     body copy slides beneath it. On a phone the headline is 33px tall and runs
     the full width, so it read straight through the wordmark. The blur stays;
     what changes is how much ground is in front of it. */
  body.scrolled .bar{background:color-mix(in srgb,var(--bg) 90%,transparent)}
  :root{--bar:60px}
  /* The wordmark is the way home, and it is the tallest thing in the bar, so
     it is the one that sets the height. 17px of type on a 26.3px line, nine of
     padding to reach a 44px target, five of that given back. */
  .mark{padding:9px 0;margin-top:-5px;margin-bottom:-5px}
  /* 13px of padding makes the 44px a thumb needs; the negative margin gives it
     back to the row, so the bar does not grow by the difference. */
  .barlink{padding:13px 0;margin-top:-5px;margin-bottom:-5px}

  /* A SMALLER RADIUS IN THE HAND, for the reason given where the token is set. */
  :root{--r-ctl:8px}
  .cue{padding:13px 0}
  /* 14px type on a 1.55 leading is a 22px line; a thumb needs about 44, so the
     padding is what makes up the difference rather than the type growing. */
  .footgrid a{display:inline-block;padding:12px 0}
  .footgrid ul{gap:0}
  /* a summary is the control, so the whole row has to be the target */
  .q summary{padding-top:clamp(19px,2.2vw,26px);padding-bottom:clamp(19px,2.2vw,26px)}
  /* NOTHING HERE HOVERS, and the rules that assumed one are guarded at their
     own definitions with @media(hover:hover) rather than being undone here.
     A tap on a touch screen leaves :hover applied until you tap somewhere
     else, so the MENU pill kept the acid tint it takes on a desktop rollover
     and read as a permanently selected control. */
    /* Type size follows the DEVICE, not the width. An 11px mono label is read at
     arm's length on a desk; every touch screen is held closer than that, and a
     1024px tablet is as much a hand-held as a 390px phone. Keying this to a
     width missed every tablet in the range. */
  .mono,.barlink,.tile .when,.dnum,.term span,
  .eyebrow,.reelword,.dlabel{font-size:12px}
}

@media (max-width: 700px) {
  /* type that is a label on a desktop and a smudge in the hand */
  .mono,.barlink,.tile .when,.dnum,.term span{font-size:12px}
  .eyebrow{font-size:12px}
  /* the reel's caption was the one left at 11.5 */
  .reelword,.dlabel{font-size:12px}
  /* the shelf hint sits under a two-line heading and does not need a measure */
  .sechead .mono{max-width:none}
}

@media (max-width: 560px) {
  /* two figures side by side and the third, which is words rather than a
     number and needs the width, across the row under them */
  .stats{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
    gap:clamp(16px,4vw,26px) clamp(18px,5vw,34px)}
  .stat:last-child:nth-child(odd){grid-column:1 / -1}
  /* GONE, not tuned: the 640px rule above sets aspect-ratio:4/5, which at 326px
     of frame is 407px of height, and a min-height of 288 can never bind on top
     of it. The frame was already square-ish; what made the drawing small inside
     it was the reserve the reel was holding for furniture that is not in this
     frame, and that is fixed where it is set, in index.html. */
  .portrait{aspect-ratio:1/1}
}

/* A phone on its side is 390px tall. A frame with a 460px floor on it means the
   section above and below are both off the screen and there is nothing to
   orient by. */
@media (max-height: 520px) and (orientation: landscape) {
  .reelframe{min-height:78vh}
  .portrait{aspect-ratio:16/9}
  /* The hero is bottom-aligned in a full viewport, which is right until the
     viewport is 390px tall - then the headline reaches the top of the screen
     and runs underneath the fixed header. A phone on its side needs the bar's
     height reserved and the hero allowed to be taller than the screen. */
  .hero{min-height:auto;padding-top:calc(var(--bar) + 40px);padding-bottom:clamp(36px,6vh,64px)}
  /* and the shelf hint has no room to sit beside a two-line heading */
  .sechead{flex-direction:column;align-items:flex-start;gap:10px}
}

/* ============================================================ the page in bands
 *
 * Everything above section 04 sits on one flat ground, and that was most of
 * why the page read as monochrome: not the absence of hue, which the covers
 * have plenty of, but the absence of any change. A reader scrolling nine
 * sections passed nine hairlines and no events.
 *
 * So the bottom third is banded, and only there, because the top two thirds
 * carry canvases. Every cover, the hero field, the globe and the reel read
 * `--c-back` and `--page` off the ROOT element and cache them, so a ground set
 * on a section underneath one of them would not reach the drawing and the
 * vignettes would halo against a colour the canvas had never heard of. The
 * three sections that are only type are the three that are free to move.
 *
 * The rhythm is flat, flat, flat, band, BLOCK, flat, foot. One deep section
 * rather than alternating stripes: alternation is a pattern and a reader stops
 * seeing it by the third repeat, whereas one inversion is an event, and it is
 * spent on help with a project, one to one: the offer that is always open,
 * whatever the workshop dates are.
 */
/* --surface and not --bg2. One step off the page ground is a value a screen
   renders and an eye does not notice; the band has to be far enough to read as
   a decision. */
#who{background:var(--surface)}

/* The deep block redefines the TOKENS rather than restating a colour on each
   rule inside it. Every element in this section already asks for --ink, --line,
   --surface; rebinding those six names at the section is one edit that reaches
   all of them, and it is the reason this block cost no changes to its markup.
   --acid-ink goes back to the full acid, because the olive that acid has to
   become as ink on paper is invisible on a ground this dark. */
/* The inversion is a CLASS, because two pages spend it now: help with a
   project on the landing page, and "the files you leave with" on a course page. It was a
   rule on #training and then a copy of that rule inlined on a <section>, which
   is two definitions of one idea and the shorter road to the third. */
.deepblock{
  background:var(--deep);
  --bg:var(--deep);
  --bg2:var(--deep2);
  --surface:var(--deep2);
  --surface2:var(--deep2);
  --line:var(--deep-line);
  --line2:var(--deep-line2);
  --ink:var(--deep-ink);
  --ink2:var(--deep-ink2);
  --ink3:var(--deep-ink3);
  --acid-ink:var(--acid);
  color:var(--deep-ink);
}
/* The two banded sections had padding-top:0, which is right when a section is
   the same colour as the one above it and wrong the moment it is not: the band
   would start on the first word of the heading. They take a shorter pad than
   the full section rhythm rather than the whole thing, so the air does not
   double at the joint. */
#who,.deepblock{--pt:clamp(56px,7vw,110px)}

/* ============================================================ not filmed yet
 *
 * THE GRID IS CSS HERE, not the canvas. The hero's live field is three hundred
 * lines of script that live inside index.html, and a page whose job is to
 * apologise does not justify a second copy of it. Two repeating gradients and
 * a dot draw the same plane: a rule at every cell edge, a node at every
 * crossing, in the same --c-line the canvas reads. It drifts one cell every
 * twenty-six seconds, which is slow enough to be noticed only if you wait, and
 * that is the whole of the play in it.
 */
.notfound{
  --cell:clamp(38px,7vw,62px);
  justify-content:center;
  background-color:var(--bg);
  background-image:
    radial-gradient(circle at 1px 1px, rgba(var(--c-line),.55) 1px, transparent 1.6px),
    linear-gradient(to right, rgba(var(--c-line),.20) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(var(--c-line),.20) 1px, transparent 1px);
  background-size:var(--cell) var(--cell),var(--cell) var(--cell),var(--cell) var(--cell)}
@media(prefers-reduced-motion:no-preference){
  .notfound{animation:drift 26s linear infinite}
}
@keyframes drift{
  to{background-position:
    var(--cell) var(--cell),var(--cell) var(--cell),var(--cell) var(--cell)}
}
/* ONE NARROW COLUMN. The page is a sentence, a field and a way out, and at the
   landing page's scale it was a wall: a headline three lines tall, a field
   1,372px wide, and four outlined buttons under it. The field is capped here
   because .form.inline switches its own cap off, which is right in the hero,
   where a column holds it, and wrong on a page that has no column. */
.notfound [hidden]{display:none!important}
.nfbox{max-width:600px}
/* A step under the section headings, so a course name three words long sets on
   two lines rather than four. */
.notfound h1{font-size:clamp(28px,3vw,44px);line-height:1.08}
.notfound .lede{margin-top:clamp(14px,1.8vw,20px);font-size:clamp(16px,1.2vw,18px)}
.notfound .form{margin-top:clamp(22px,2.6vw,30px);max-width:440px}
.notfound #nfbook{display:inline-block;margin-top:clamp(22px,2.6vw,30px)}
/* The ways out are a sentence rather than four buttons: the acid on this page
   belongs to the one thing worth doing here, and a row of buttons under it
   competed with it for no reason. */
.nfmore{margin:clamp(24px,3vw,34px) 0 0;color:var(--ink2);font-size:15px;line-height:1.7}
.nfmore a{color:var(--ink);text-decoration:underline;text-decoration-color:var(--acid-ink);
  text-underline-offset:3px;transition:color var(--t1)}
@media(hover:hover){.nfmore a:hover{color:var(--acid-ink)}}

/* ============================================================ the mail chooser */
.mailpick{position:fixed;z-index:140;min-width:212px;padding:6px;
  background:var(--bg2);border:1px solid var(--line2);border-radius:4px;
  box-shadow:0 18px 44px -18px rgba(0,0,0,.45);
  display:flex;flex-direction:column;gap:1px}
.mailpick[hidden]{display:none}
.mailaddr{margin:0;padding:9px 12px 10px;color:var(--ink3);font-size:11px;
  letter-spacing:.04em;text-transform:none;border-bottom:1px solid var(--line)}
.mailopt{display:block;width:100%;text-align:left;padding:11px 12px;border:0;
  background:transparent;border-radius:3px;cursor:pointer;color:var(--ink);
  font-family:var(--display);font-size:14px;font-weight:400;
  transition:background var(--t1),color var(--t1)}
.mailopt:focus-visible{background:var(--surface);color:var(--acid-ink)}
@media(hover:hover){.mailopt:hover{background:var(--surface);color:var(--acid-ink)}}
.mailopt.done{color:var(--acid-ink)}
/* On a phone it is a sheet at the bottom of the screen rather than a popover
   pinned to a 40px button: a thumb reaches the bottom of a screen and does not
   reach a menu hanging off the top of one. */
@media (max-width: 640px){
  .mailpick{left:12px;right:12px;bottom:12px;top:auto !important;min-width:0;
    padding:8px;border-radius:8px}
  .mailopt{padding:15px 14px;font-size:16px}
}

