/* intro — the page that presents the studio.

   Laid out the way the project details in the portfolio are: a column of copy
   that scrolls as ordinary page content, and beside it the thing itself, held
   still with position: sticky. One scroller, not two - the wheel then works
   from anywhere over the page with the browser's own scrolling, inertia and
   rubber-band and all, and nothing has to be forwarded by hand.

   Paper and ink from the app itself: white, black, one grey, two typefaces,
   and the offset shadow every button and frame in the studio wears. Nothing
   here is shared with the app's stylesheets on purpose - this page must keep
   working whatever the panel's CSS does next. */

:root {
  --bg: #ffffff;
  --ink: #000000;
  --muted: #6b6b6b;
  --faint: #d4d4d4;
  --wash: #f4f4f2;

  --ui-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --ui-sans: "Archivo", "Helvetica Neue", Helvetica, Arial, sans-serif;
  /* The face the titles are set in, and the only thing on the page that is:
     the page name, the section titles and the part titles. Lexend is drawn
     wide and open, with a lot of room inside the letters, which is what a
     title set large and in capitals wants and what Archivo - a good face for
     running text - gives up at that size. Kept as a token because three rules
     read it and a fourth will. */
  --ui-display: "Lexend", "Archivo", "Helvetica Neue", Helvetica, Arial, sans-serif;

  --gutter: clamp(20px, 3vw, 40px);
  /* The room above the page title: this on a narrow screen, and on a wide one
     until intro.js has measured the frame's top into --intro-pad, which puts
     the rule under the title on the frame's line. */
  --head-room: clamp(48px, 7vw, 120px);

  /* The two marks on the cycle control, inlined as masks so they take
     currentColor the way every button mark in the studio does. Dashicons
     controls-pause and controls-play (GPL-2.0-or-later, see the colophon). */
  --icon-pause: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M5 16V4h3v12H5zm7-12h3v12h-3V4z'/%3E%3C/svg%3E");
  --icon-play: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M5 4l10 6-10 6V4z'/%3E%3C/svg%3E");
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--ui-sans);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* ---- shown once, settled ----
   Between the first paint and the page being itself there are three changes
   worth not watching: Archivo arriving and reflowing every heading, the
   default texture scene going on, and the two of them landing in either
   order. Covered until they have, then faded off.

   A cover rather than fading the body: opacity below 1 on the body would make
   it the containing block for everything fixed inside it, which is the panel
   and the frame's own furniture. The cover is a pseudo-element of the root,
   so nothing in the page is touched by it.

   Two classes, not one: a pseudo-element cannot fade out as its class is
   removed — it simply stops existing. So the first holds it opaque and the
   second fades it, and only then does it go. */
html.is-settling::after,
html.is-revealing::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 100;
  background: var(--bg);
  pointer-events: none;
  transition: opacity .3s ease;
}

html.is-settling::after { opacity: 1; }
html.is-revealing::after { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  html.is-settling::after, html.is-revealing::after { transition: none; }
}

/* ============================ the two columns ============================ */
.columns { display: grid; grid-template-columns: 1fr; }

@media (min-width: 1000px) {
  .columns {
    /* The proportion the project details use: a panel of copy against the work
       filling everything left over. */
    grid-template-columns: minmax(20rem, 34%) 1fr;
    /* Not stretched: a grid item that fills its row cannot stick inside it. */
    align-items: start;
  }

  /* The copy on the left, the studio on the right. Placed explicitly so the
     order on a wide screen is said here, not left to the markup. */
  .copy { grid-column: 1; grid-row: 1; }
  .showcase { grid-column: 2; grid-row: 1; }
}

/* ---- column 1: the copy ----
   A deep head and foot. The column scrolls, so the text starts below the fold
   of its own box and ends well before the bottom of it, which is what keeps it
   off the edges of a panel that has no other chrome.

   Edge to edge: the column carries no padding of its own. Every bit of space
   in it belongs to one of its sections, and the sections abut, so each one is
   a full-width band from its rule to the next - which is what lets a texture
   on a section (see texture-kit.js) fill it cleanly instead of stopping at the
   gutter or leaving the gap between two sections bare. */
.copy > section {
  position: relative;   /* the rule below is drawn from here */
  margin: 0;
  padding: 0 var(--gutter) 2rem;
}
.copy > section:first-child { padding-top: var(--head-room); }
.copy > section:last-child { padding-bottom: calc(2rem + clamp(64px, 8vw, 128px)); }
/* The 2rem between sections was a margin, and the last line's own margin
   collapsed into it; as padding it would add to it instead. Every such margin
   here is smaller than the 2rem, so dropping it gives the same gap. */
.copy > section > :last-child,
.copy > .intro > .role:last-child { margin-bottom: 0; }   /* the second outranks `.copy p.role` */

/* A rule between one section and the next, the same weight as the one under
   the head: the panel is a long column of lists and they run together without
   something to say where one subject ends.

   Drawn rather than a border, so it can keep to the gutters while the section
   itself runs to the column's edges - and so it stays on top of a texture laid
   under the section's content. */
/* Plus the 2px the rule took up in the flow when it was a border. */
.copy > section + section { padding-top: calc(1.75rem + 2px); }
.copy > section + section::before {
  content: "";
  position: absolute;
  top: 0;
  left: var(--gutter);
  right: var(--gutter);
  height: 2px;
  background: var(--ink);
}

.copy p { margin: 0 0 .9rem; font-size: .9375rem; line-height: 1.6; }
/* The portfolio's entry-points list (web-portfolio/app/styles/_devices.scss):
   small mono copy, square markers hung out of the text block so a wrapped line
   returns to the words and not under the square. */
.copy ul {
  display: flex;
  flex-direction: column;
  gap: .65rem;
  margin: .6rem 0 0;
  padding: 0;
  list-style: none;
}
.copy li {
  position: relative;
  padding-left: 1.5rem;
  font-family: var(--ui-mono);
  font-size: .8rem;
  line-height: 1.5;
  color: var(--ink);
}
.copy li::before {
  content: "";
  position: absolute;
  top: .55em;
  left: 0;
  width: 6px;
  height: 6px;
  background: var(--ink);
}

/* The steps, in both parts: the lists on the page meant to be read at a
   glance, so they drop the markers (the numbers are in the text) and are set
   large in the page face, the way the portfolio sets its band titles. Beside
   the frame in part 1, as wide as the longest step - a step down from the 2rem
   they had under it, which is what lets the two share a line - and the same
   again in part 2, so the two parts read as one device. */
.steps {
  flex: none;
  display: grid;
  justify-items: start;
  align-content: center;
  gap: .35em;
  margin: 0;
  padding: 0;
  list-style: none;
  text-align: left;
}
/* Direct children only. The second part's steps carry a list of kits inside
   one of them, and a bare `.steps li` reached those too - at three classes it
   outweighed every rule the kits set for themselves, so they came out in the
   step's face and at the step's size however they were written. */
.steps > li {
  font-family: var(--ui-sans);
  font-size: clamp(1.125rem, 1.7vw, 1.75rem);
  line-height: 1;
  letter-spacing: -.035em;
  font-weight: 700;
}
/* ---- the plate this part is printed on ----
   White, mostly opaque, floated over whatever texture the page is wearing.
   This is the one part of the column with steps to follow and a button to
   press, and small type over a pattern is the wrong thing to ask anyone to
   read. Translucent rather than solid, so the texture still shows through and
   the box reads as paper laid on the page rather than as a hole cut in it.

   A box around the content, not a band across the part. It takes the cell the
   title and the body used to share - both grid rows, so the number beside it
   still centres against the whole of it - and is as tall as what is in it and
   no taller. It wears the offset shadow every frame and button on this page
   does, so it floats rather than sits.

   There were two of these, side by side at half the part each: the steps and
   the demo. The steps moved to the reading column, where they are read rather
   than glanced at, and one box in a half-width cell is a box with an empty
   half beside it - so this one takes the part's whole width. Centred down the
   part rather than pinned to the top of it, because half a screen with a short
   box at the top of it is mostly a gap.

   Real content, so it paints over the texture kit's layers without anything to
   arrange: those go in a host div at z-index -1, under the element's content
   and over its background. Which is also why the plate is not the element's
   own background colour - that is the kit's control, written to this section
   inline whenever a scene names a colour. */
.part-use .plate {
  grid-column: 2; grid-row: 1 / 3; align-self: center; min-width: 0;
  padding: clamp(1rem, 2vw, 1.75rem);
  background: #ffffffd9;
  box-shadow: 3px 5px #00000033;
  /* One typeface inside a plate. Everything here is a small, close-read thing
     - a title, four steps, three kit names, two buttons - and the page's other
     two faces were each doing a job that does not apply at this size: Lexend
     800 carries a half-screen title, Archivo 700 carries a step read at a
     glance. The mono is what the left column reads its own lists in, and it is
     what the buttons here were already set in. */
  font-family: var(--ui-mono);
  /* What the title measures itself against: the plate's own width, not the
     window's. The two plates are halves of a column that is itself a fraction
     of the page, so a vw would say nothing useful about the room in here. */
  container-type: inline-size;
}
/* The two lists take the plate's mono, and their tracking goes with them:
   both were tightened for a proportional face set large, and mono at this size
   wants its own.

   The title does not. It is a title, and the page sets its titles in the
   display face wherever they are - a plate is a place on the page, not a
   reason to rename the thing standing at the top of it. */
.part-use .plate .steps > li,
.part-use .plate .kits li { font-family: inherit; letter-spacing: normal; }
.part-use .plate .part-title { font-family: var(--ui-display); }
/* 700 is the heaviest mono the page loads (see the font link in index.html);
   800 would be drawn by the browser rather than by the face.

   One line, always. The title is a sentence with a colon in the middle of it
   and it breaks in the wrong place when it breaks at all, so it is told not to
   and sized to the plate instead: cqw against the plate's own width, which is
   half the column and nothing to do with the window. The coefficient is the
   measured width of this string in this face at a known size - change the
   words OR the face and it wants measuring again. The rem caps it where the
   plate is wide enough that following the box would only make it shout.

   Measured again for each face and each case it has worn: 19.8em in the
   plate's old JetBrains Mono 700, 19.87em when it moved to Lexend 700, and
   15.82em now that it is capitalised rather than set in capitals. Lower case
   is narrower, so the same plate carries the title a quarter larger than it
   did - which is most of the increase, and it is bought rather than forced.

   6.1cqw against a measured 6.32, and 25.2px as the ceiling: that is the size
   chosen in the kit, and the cqw is what stops a half-plate being asked to
   hold it. At this window the plate gives about 19px and the title fits; on a
   wider one it climbs to 25.2px and stops there.

   Scoped under .showcase to outrank the wide-screen rule further down this
   file, which sizes the part titles off the window and weighs the same three
   classes - on a tie the later rule wins, and it was leaving the title at the
   window's size to overflow the plate rather than fit it. */
.showcase .part-use .plate .part-title {
  font-weight: 700;
  word-spacing: normal;
  white-space: nowrap;
  text-transform: capitalize;
  font-size: min(25.2px, 6.1cqw);
}
.part-use .plate .steps > li { position: relative; padding-left: 2.1em; line-height: 1.45; }
/* ---- the step numbers, boxed ----
   Not scoped to the part: the same steps are repeated in the reading column
   (see .aside-part) and the chips belong to the steps, not to where they are.
   Inverted squares down the left edge. Set in the same mono as the line beside
   them, a whole numbered list of short instructions came out as one grey block
   with no way in; a black chip per step gives the eye somewhere to land and a
   place to come back to after following one.

   Written out in index.html rather than counted by CSS: a step can be
   commented out while it is being decided (step 3 is, today), and a counter
   would quietly renumber the rest behind it. The number in the markup is the
   number on the page.

   Square by construction: the box is 1.45em on both sides, which is the line
   height beside it, so it fills exactly one line of the step and no more -
   including a step that wraps onto two. */
.steps > li .num {
  position: absolute;
  left: 0;
  top: 0;
  display: grid;
  place-items: center;
  width: 1.45em;
  height: 1.45em;
  background: var(--ink);
  color: var(--bg);
  font-size: .9em;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* The second part's steps at a flat 1rem. They are instructions to follow
   rather than a list to take in at a glance, and they sit under a title that
   is now half the size it was — scaling them with the window put them level
   with it. The studio's list keeps the ramp: it is read across the page. */
.part-use .steps > li { font-size: .875rem; }

.part-use .actions { margin-top: 1.5rem; }
/* Not the buttons: a bare .button rule loses to this one on specificity - a
   class plus a type beats a class - and the primary button came out black on
   black. */
.copy a:not(.button) { color: var(--ink); }

/* A section that earns a title rather than a label. The others name themselves
   inline, in the code chip the whole column is written in, which is right for
   a line of detail; this one is the section someone is actually looking for,
   so it says so above the text rather than at the head of it. */
.section-title {
  margin: 0 0 1.5rem;
  font-family: var(--ui-display);
  font-size: 2rem;
  line-height: 1.1;
  letter-spacing: -.025em;
  /* Capitalised rather than set in capitals. */
  font-weight: 700;
}

/* The device the whole panel is written in: a name, a tool or a term set apart
   from the sentence around it. Quiet - a tint and a little air, not a border -
   because a line here can hold four of them. */
code {
  font-family: var(--ui-mono);
  font-size: .8125em;
  background: var(--wash);
  border-radius: 2px;
  padding: .15em .4em;
  white-space: nowrap;
}

kbd {
  display: inline-block;
  min-width: 18px;
  padding: 1px 5px;
  border: 1px solid var(--ink);
  background: var(--bg);
  font-family: var(--ui-mono);
  font-size: .8em;
  text-align: center;
}

/* ---- the head of the panel ----
   A query container, so the title can be sized as a share of the column
   rather than of the window. The column is a percentage of the viewport with a
   floor, so vw never described it properly. On the head rather than on the
   column, because the head's content box is the column less its gutters -
   the measure the title is fitted to. */
.intro { container-type: inline-size; }
/* Set to the measure rather than to a taste, and to the WHOLE name on one
   line. Randomize Studio sets 8.67em wide in this face at this weight, and the
   -0.04em tracking takes 0.6em back off its fifteen gaps, so it comes to
   8.07em: 100/8.07 = 12.4% of the column is the size at which it fills the
   line exactly.

   12.2 rather than 12.4, because the fallback stack is the wider one - 8.12em
   against Archivo 8.07 - and the title is on screen in it for the moment
   before the webfont lands. Sizing to the widest of the two means the line
   never has to reflow.

   nowrap on top of that: a measurement is a prediction, and this one is made
   against fonts that a browser is free to substitute. The size keeps it from
   needing to wrap; this keeps it from wrapping anyway.

   The clamp above is what a browser without container units gets, and the cap
   keeps it from running away on a very wide screen. */
.title {
  margin: 0;
  font-family: var(--ui-display);
  font-size: clamp(1.5rem, 2.6vw, 2rem);
  /* 38px is the size chosen in the kit. It is the CEILING rather than the
     size, because this title is set nowrap and the cqw is what keeps it on
     one line in a column that is a fraction of the page - a flat 38px would
     hold that size right up to the point where the title ran out of column. */
  font-size: clamp(1.5rem, 12.2cqw, 38px);
  line-height: 1.02;
  letter-spacing: -.04em;
  font-weight: 700;
  white-space: nowrap;
}

.role {
  margin: .65rem 0 0;
  font-family: var(--ui-mono);
  font-size: .75rem;
  letter-spacing: .1em;
  text-transform: lowercase;
  color: var(--ink);
}

/* `.copy p` outranks a bare `.role` (class + type beats class), so the type
   has to be set at the same weight to take. */
.copy p.role {
  margin: 0 0 .9rem;
  font-size: .85rem;
  line-height: 1;
  font-weight: 900;
}

/* How big the thing is, in figures. Bold rather than set out in a table of
   their own: the panel is a column of prose and this is the one line in it
   that should be readable at a glance. */
.scale {
  margin: 1rem 0 0;
  font-size: .9375rem;
  line-height: 1.5;
  color: var(--muted);
}

.scale strong { font-weight: 700; color: var(--ink); }

/* The line that separates what the thing IS from what is in it. */
.intro-rule { margin: 1.25rem 0; border: 0; border-top: 2px solid var(--ink); }

/* A screenshot in the column, wearing the same rule and shadow as the frame
   beside it, so the two read as the same kind of object. */
.shot { margin: 1.25rem 0 0; }

/* Capped at 260: it is a screenshot of a 356px panel, and past its own size it
   is only a bigger blur. */
.shot img {
  display: block;
  width: 100%;
  max-width: 260px;
  height: auto;
  border: 2px solid var(--ink);
  box-shadow: 3px 5px #00000033;
}

.shot figcaption {
  margin: .7rem 0 0;
  font-family: var(--ui-mono);
  font-size: 10px;
  line-height: 1.6;
  letter-spacing: .06em;
  color: var(--muted);
}

/* The stack is a row of chips rather than a sentence, so it wraps as one. */
.stack-section {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .4rem;
}

.colophon p { font-size: .8125rem; line-height: 1.7; color: var(--muted); }

/* The tools a coding agent can be sent for, under the step that sends it.
   The copy column's square, not a disc and not a number: the steps around them
   are numbered, and a second kind of counter reads as a second kind of order.
   Each line names a kit and offers the press that copies its prompt. */
.kits {
  margin: .5em 0 .1em;
  /* Indented past the numbers of the steps they hang under, so the squares
     start inside the list rather than in line with its counters. */
  padding: 0 0 0 .9em;
  list-style: none;
  display: grid;
  gap: .45em;
}
/* The copy column's type, not the step's. The steps are set large, bold and
   tight because each is a line to take in at a glance; these are three names
   with a control on each, read one at a time — which is the left column's job
   exactly, so they are set the way it sets its own bulleted lists. */
.kits li {
  position: relative;
  padding-left: 1.3em;
  font-family: var(--ui-mono);
  /* Two steps under the left column's .8rem: mono sets wide, and the line has
     to hold a kit name, a colon, a mark and two more words on one line inside
     half a plate. */
  font-size: .7rem;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: normal;
}
/* Centred on the first line rather than hung at a fixed height: half the line
   box, less half the square. */
.kits li::before {
  content: "";
  position: absolute;
  top: calc(.75em - 3px);
  left: 0;
  width: 6px;
  height: 6px;
  background: var(--ink);
}

/* Nothing to copy yet — see the note in intro.js. Dimmed rather than gone,
   because the list is the shape the thing is heading for. */
.copy-prompt[disabled] { opacity: .3; cursor: not-allowed; }
.copy-prompt[disabled]:hover { opacity: .3; }

/* ---- copy the agent prompt ----
   The mark and the words that say what it does, after the name of the kit it
   belongs to. Dashicons admin-page, inlined as a mask so it takes currentColor
   like every other mark in this project.

   The mark is drawn on ::before rather than on the button, because the button
   carries its own label and has to be able to say what the press did — and a
   mask on the element would cut both away with everything outside the glyph.

   Inline rather than a flex row: the mark is an inline-block sat on the text's
   baseline by hand, which keeps the whole button on the line of the kit name
   beside it. A flex container would baseline on the mark instead, and the mark
   has no text in it to have a baseline. */
.copy-prompt {
  --icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M6 15V2h10v13H6zm-1 1h8v2H3V5h2v11z'/%3E%3C/svg%3E");
  position: relative;
  margin-left: 2px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  /* The line's type, at its normal weight and underlined: the kit name beside
     it is the label and this is the thing you press, so it is the one word on
     the line that should look like a link rather than like more of the name. */
  font: inherit;
  font-weight: 400;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.copy-prompt::before {
  content: "";
  display: inline-block;
  width: 15px;
  height: 15px;
  margin-right: 5px;
  vertical-align: -3px;
  background: currentColor;
  -webkit-mask: var(--icon-copy) center / contain no-repeat;
  mask: var(--icon-copy) center / contain no-repeat;
}

.copy-prompt:hover { opacity: .6; }

/* What the press did, said beside it and gone again. An attribute rather than
   a class: the word itself is the state. Absolute, so a word appearing does
   not move the step it is attached to. */
.copy-prompt[data-said]::after {
  content: attr(data-said);
  position: absolute;
  left: calc(100% + 6px);
  top: 0;
  font-family: var(--ui-mono);
  font-size: 10px;
  line-height: 15px;
  letter-spacing: .1em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--muted);
}

/* ---- buttons ----
   The studio's own: ink on paper, two-pixel border, the shadow offset down and
   to the right. They stay put under the pointer - no push into the shadow. */
.actions { margin: 1.5rem 0 0; display: flex; flex-wrap: wrap; gap: 10px; }

.button {
  display: inline-block;
  padding: 11px 20px;
  border: 2px solid var(--ink);
  background: var(--ink);
  color: var(--bg);
  font-family: var(--ui-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .12em;
  text-transform: uppercase;
  text-decoration: none;
  box-shadow: 3px 5px #00000033;
  cursor: pointer;   /* a <button> does not get this for free the way a link does */
}

.button.quiet { background: var(--bg); color: var(--ink); }

/* Randomize this page: a paper rule inside the ink edge, so the button holds
   its shape on a textured band as well as on paper - and it wears a texture of
   its own, the one the kit was pointed at it to make. The button that starts
   the thing is the one place on the page that should look like the thing.

   The layer is what Copy CSS wrote out: a row of circles in the palette it was
   rolled in, over the orange the roll left behind it. Pasted rather than kept
   in the scene file, because this one is the page's own look rather than
   something the kit should put back every visit.

   z-index -1 under isolation puts it over the button's black background and
   under its label, the same sandwich every kit layer sits in. It was turned 15
   degrees in the panel; flat CSS cannot carry that - one pseudo-element cannot
   cover its own corners when turned - so it is square on here.

   The hover keeps the old roll, on the artwork instead of on a colour: the
   tile is 600 wide with a circle every 100, so stepping 100 a sixth runs the
   whole row through in the same 1.08s it always took. */
.button.randomize-page {
  position: relative;
  isolation: isolate;
  border-color: var(--bg);
  box-shadow: 0 0 0 2px var(--ink), 3px 5px #00000033;
}
.button.randomize-page::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-color: #f2ab27;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='scale' width='600' height='100' viewBox='0 0 600 100'%3E%3Cg class='color8' stroke='%23f2ab27' stroke-width='0' stroke-miterlimit='10' stroke-opacity='1'%3E%3Ccircle class='color2' fill='%2300506a' cx='0' cy='0' r='50'/%3E%3Ccircle class='color3' fill='%232d7dec' cx='100' cy='0' r='50'/%3E%3Ccircle class='color4' fill='%23f07666' cx='200' cy='0' r='50'/%3E%3Ccircle class='color5' fill='%23ee41a4' cx='300' cy='0' r='50'/%3E%3Ccircle class='color6' fill='%23028dde' cx='400' cy='0' r='50'/%3E%3Ccircle class='color7' fill='%2302697e' cx='500' cy='0' r='50'/%3E%3Ccircle class='color2' fill='%2300506a' cx='600' cy='0' r='50'/%3E%3Ccircle cx='-50' cy='50' r='50'/%3E%3Ccircle class='blend23' fill='%2303758f' cx='50' cy='50' r='50'/%3E%3Ccircle class='blend34' fill='%23d824d6' cx='150' cy='50' r='50'/%3E%3Ccircle class='color1' fill='%23f2ab27' cx='250' cy='50' r='50'/%3E%3Ccircle class='blend56' fill='%238250e1' cx='350' cy='50' r='50'/%3E%3Ccircle class='blend67' fill='%230381b7' cx='450' cy='50' r='50'/%3E%3Ccircle class='blend72' fill='%23015c74' cx='550' cy='50' r='50'/%3E%3Ccircle cx='650' cy='50' r='50'/%3E%3Ccircle class='color2' fill='%2300506a' cx='0' cy='100' r='50'/%3E%3Ccircle class='color3' fill='%232d7dec' cx='100' cy='100' r='50'/%3E%3Ccircle class='color4' fill='%23f07666' cx='200' cy='100' r='50'/%3E%3Ccircle class='color5' fill='%23ee41a4' cx='300' cy='100' r='50'/%3E%3Ccircle class='color6' fill='%23028dde' cx='400' cy='100' r='50'/%3E%3Ccircle class='color7' fill='%2302697e' cx='500' cy='100' r='50'/%3E%3Ccircle class='color2' fill='%2300506a' cx='600' cy='100' r='50'/%3E%3Ccircle cx='50' cy='150' r='50'/%3E%3Ccircle cx='150' cy='150' r='50'/%3E%3Ccircle cx='250' cy='150' r='50'/%3E%3Ccircle cx='350' cy='150' r='50'/%3E%3Ccircle cx='450' cy='150' r='50'/%3E%3Ccircle cx='550' cy='150' r='50'/%3E%3C/g%3E%3C/svg%3E");
  background-attachment: scroll;
}
.button.randomize-page:hover::before { animation: randomize-roll 1.08s steps(1, end) infinite; }

/* Explore typography: the other tool, and the Randomize button's peer rather
   than its follower - the two are separate kits and the demo offers both. It
   wears the white rule inside the ink edge for the same reason that one does,
   but no texture of its own: the textured button is the texture kit saying
   what it does, and a second patterned button beside it would say nothing
   except that there are two patterns. */
.button.explore-type,
.button.explore-move-resize {
  border-color: var(--bg);
  box-shadow: 0 0 0 2px var(--ink), 3px 5px #00000033;
  transition: background-color .12s ease;
}
/* A clear lift on hover: ink to a mid charcoal, enough to read as a change at
   a glance rather than a shade only noticed side by side. The Randomize button
   takes it too, round its colour tile. */
.button.explore-type:hover, .button.explore-move-resize:hover, .button.randomize-page:hover { background: #4d4d4d; }

/* Each tool's mark in front of its label: the type frame with its corner
   handles, and the four-way move arrow. Drawn in currentColor, so they are
   paper on ink like the label and follow it on hover. The label wraps beside
   the mark rather than under it. */
.button.explore-type,
.button.explore-move-resize {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
}
.button.explore-type .icon,
.button.explore-move-resize .icon { flex: none; width: 32px; height: 32px; }

/* The mark sits on a square tile of its own with the colours turned round:
   ink lines on paper, in front of a paper label on ink. The tile keeps its
   paper on hover, so only the label's ground changes. */
.button.explore-type .icon,
.button.explore-move-resize .icon { background: var(--bg); color: var(--ink); }

/* Flush in the button rather than padded inside it: the tile is the button's
   full inner height on the left, so the 32px mark sets the height instead of
   adding the padding to it. More specific than .actions.pair .button, which
   sets the padding every button in the row has. */
.actions.pair .button.explore-type,
.actions.pair .button.explore-move-resize { padding: 0 10px 0 0; justify-content: flex-start; }
/* The mark stays against the left edge however wide the row makes the
   button; the label takes the rest and centres itself in it. */
.actions.pair .button.explore-type .label,
.actions.pair .button.explore-move-resize .label { flex: 1 1 auto; min-width: 0; text-align: center; }

/* Randomize this page, built the same way as its two neighbours: the label on
   ink, and the colour only in a square at the left where their marks sit. The
   same circles, scaled down to the tile (600x100 to 192x32), so the hover roll
   steps a circle at a time at the new size - 32px rather than 100. */
.actions.pair .button.randomize-page {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 10px 0 41px;   /* the 32px tile, its 2px rule and the 7px gap the marks have */
}
/* The tile is closed off from the label by the same 2px paper rule that runs
   inside the button's ink edge, so the colour reads as a square set into the
   button rather than a stain at its end. */
.actions.pair .button.randomize-page::before {
  inset: 0 auto 0 0;
  box-sizing: content-box;
  width: 32px;
  border-right: 2px solid var(--bg);
  /* At rest the tile is framed on the coral-red circle, with the blue, pink,
     violet and orange ones round its edges - the warm end of the palette
     rather than the teal corner the row starts on. */
  background-position: -48px 0;
  background-size: 192px 32px;
}
@media (prefers-reduced-motion: no-preference) {
  .actions.pair .button.randomize-page:hover::before { animation-name: randomize-roll-tile; }
}
@media (prefers-reduced-motion: reduce) {
  .actions.pair .button.randomize-page:hover::before { animation: none; }
}

/* From the resting frame, so the roll starts where the tile already is. */
@keyframes randomize-roll-tile {
  0%     { background-position: -48px 0; }
  16.66% { background-position: -80px 0; }
  33.33% { background-position: -112px 0; }
  50%    { background-position: -144px 0; }
  66.66% { background-position: -176px 0; }
  83.33% { background-position: -16px 0; }
}

/* The second tool's row, under the first: one gap between the two buttons of
   a pair, a smaller one between the pairs, so each reads as a tool with its
   skill beside it rather than as four buttons in a block. */

@keyframes randomize-roll {
  0%     { background-position: 0 0; }
  16.66% { background-position: -100px 0; }
  33.33% { background-position: -200px 0; }
  50%    { background-position: -300px 0; }
  66.66% { background-position: -400px 0; }
  83.33% { background-position: -500px 0; }
}

@media (prefers-reduced-motion: reduce) {
  .button.randomize-page:hover::before { animation: none; }
}

/* ============================ column 2: the studio ============================
   Two numbered parts, stacked, each the column's full width - the same way
   the copy column is built out of full-width bands, so a texture on a part
   fills it. Part 1 is the studio itself: the live cover with its five steps
   beside it. Part 2 is using it on a page of your own.

   On a wide screen the whole column is held still while the copy goes by -
   sticky, one screen tall, the two parts splitting it half and half, at any
   height. What is in them scales with the height as well as the width (the
   number, the titles, the steps, the frame), so a short window gets a smaller
   column rather than a cropped one. */
.showcase {
  display: flex;
  flex-direction: column;
}

@media (min-width: 1000px) {
  .showcase {
    position: sticky;
    top: 0;
    height: 100svh;
    overflow: hidden;   /* anything taller than its half is cropped, not scrolled to */
  }
  .part { flex: 1 1 0; min-height: 0; }
  /* The frame is sized by the height it has as well as by the width: the
     column, less its padding, less the room the title, the five steps and the
     button need under it, at 16:9. It is nearly always the width that decides
     - the panel is taking a third of the column - and this is the short-window
     case, where the frame has to give the steps their space back rather than
     push them off the bottom of a column that crops. 21rem is what the aside
     measures at the sizes below. */
  /* Half what it was - 19vw is the 50%-of-the-column it measured at, said as a
     size of its own now that the column around it is no longer a fixed share
     of the part (see .part-body's justify-self below: a percentage here would
     be measuring itself against a width that is measuring itself against
     this). The frame's height follows its width through --frame-scale, so the
     16:9 needs no saying either way.

     The max-width is the short-window guard it always was: the column, less
     its padding, less the room the title, the five steps and the button need
     under it. */
  .part-studio .stage {
    width: clamp(200px, 19vw, 420px);
    max-width: calc((100svh - 2 * var(--gutter) - 21rem) * 16 / 9);
  }
  /* The panel's own short-window guard, said in svh now that its strip is the
     height of the picture rather than the height of the part - a percentage
     has nothing definite left to measure against. The column, less its
     padding, which is as tall as anything in here may be before the column
     (overflow: hidden) starts cropping instead of the box. */
  .shot-box { max-height: calc(100svh - 2 * var(--gutter)); }
  /* Centred in the column rather than filling it. A grid item that is not
     stretched takes fit-content, so the body ends up the width of the panel,
     the gap and the widest line under the frame - its real width - and the
     column puts what is left over on both sides of it instead of all of it
     down the right.

     Only here. On one column the body is the page's width and there is nothing
     to centre it in. */
  .part-studio .part-body { justify-self: center; }
  /* The line under the page title on the frame's line, rather than the title
     itself: the rule is the edge the two columns share, and two columns that
     start their content at the same height still look unaligned if the marks
     across them do not meet.

     The frame is centred down its column, so where its top falls depends on
     the window; intro.js measures it, takes off the height of this section
     below its own top padding, and writes what is left as --intro-pad. The
     head-room is only until it has, and on one column, where there is no
     second column to meet. */
  .copy > section:first-child { padding-top: var(--intro-pad, var(--head-room)); }
  /* Sized by the height too, so both halves still fit a short window. */
  /* Under .showcase so they outrank the base rules further down the file. */
  .showcase .part-number { font-size: clamp(4rem, min(14vw, 24svh), 14rem); }
  .showcase .part-title { font-size: clamp(1.75rem, min(3.4vw, 6svh), 3.5rem); margin-bottom: min(1.1rem, 1.8svh); }
  /* Half of that for the second part - see the note by .part-use .part-title.
     Scoped under .showcase as well, or the rule above would outweigh it. */
  .showcase .part-use .part-title { font-size: clamp(.875rem, min(1.7vw, 3svh), 1.75rem); }
  .steps > li { font-size: clamp(1rem, min(1.7vw, 3.2svh), 1.75rem); }
  /* Under .showcase too, or the rule above outweighs the flat size. */
  .showcase .part-use .steps > li { font-size: .875rem; }
  .part-use .actions { margin-top: min(1.5rem, 2.5svh); }
}

/* A part: the big number in its own column down the left, and the title and
   the body beside it. */
.part {
  position: relative;   /* the rule between the parts is drawn from here */
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
  column-gap: clamp(16px, 2.5vw, 40px);
  padding: var(--gutter);
}
/* Numbers hidden (the `hidden` on them in index.html): their column is empty
   and takes no width, so drop the gap after it too and the part's content
   starts at the gutter. Take `hidden` off to bring them back. */
.part:has(> .part-number[hidden]) { column-gap: 0; }
/* No rule between the two parts. The copy column draws one between its
   sections because they are a list of short subjects on one page; these two
   are half a screen each and already read as separate — a line across the
   middle only cuts the texture that runs behind them.

   Their ::before is free because of it, which is why the kit no longer counts
   it as taken: a texture can be laid there like anywhere else. */
/* Centred down the height of its part, and in a column of the same width in
   both - a 1 is narrower than a 2, and without it the two parts' titles would
   start at different places. .62em is a digit's width in this face, bold. */
.part-number {
  grid-row: 1 / 3;
  align-self: center;
  width: .62em;
  text-align: center;
  font-family: var(--ui-sans);
  font-size: clamp(6rem, 14vw, 14rem);
  font-weight: 700;
  line-height: .78;
  letter-spacing: -.06em;
}
/* A part's title: the copy column's section title, a size up and at the
   heaviest weight, since it heads a half of the column rather than a section
   of the copy. 900 is a drawn weight - index.html loads Archivo up to it. */
.part-title {
  grid-column: 2;
  margin: 0 0 1.1rem;
  font-size: clamp(2.25rem, 3.4vw, 3.5rem);
  /* The display face, like the other two titles. It was Inter, for the same
     reason Lexend is here now - open shapes and a wide set read far better at
     this weight and in capitals than the page's Archivo does - and the three
     titles being one face is worth more than the difference between them.
     Looser letters, a real gap between words, and a little more lead for a
     title that wraps. */
  font-family: var(--ui-display);
  line-height: 1.05;
  letter-spacing: -.01em;
  word-spacing: .18em;
  text-transform: uppercase;
  font-weight: 800;
}
/* The second part's title, at half the first's. THE STUDIO is a name and
   carries the top of the page; ANY PROJECT. AI-ASSISTED. is a claim standing
   over five numbered steps, and at the shared size it shouted over the thing
   it was introducing. Halved it still leads the steps — uppercase, 800, in
   Lexend against their sans — without taking the half-screen for itself.

   Exactly half the ramp above, at both ends, so the two titles keep their
   relationship at every window size rather than only at one. */
.part-use .part-title { font-size: clamp(1.125rem, 1.7vw, 1.75rem); }

.part-body { grid-column: 2; min-height: 0; }

/* The part's body: the panel down the left, and everything else beside it. It
   has no title row of its own - the title heads the steps - so the body takes
   the part's full height, beside the number.

   Two axes rather than one. The frame and the steps were side by side, which
   made the frame as wide as the steps let it be; with the steps under it the
   frame answers to the column instead, and the panel gets a full-height strip
   of its own to stand in - which is what it is, a tall thing docked down an
   edge. */
/* Both start on the same line. The panel and the cover are the two pictures
   in the column and they are different heights, so centring them against each
   other left the shorter one floating with the taller one's slack split above
   and below it - two things at two different starting points, and no line
   anywhere for the eye to hold.

   Top-aligned inside a box that is itself centred down the part: the group
   keeps the middle of the column (align-self, which also makes the body as
   tall as what is in it rather than as tall as the row), and inside it the
   panel and the frame begin together. */
.part-studio .part-body {
  grid-row: 1 / 3;
  align-self: center;
  display: flex;
  align-items: flex-start;
  gap: clamp(24px, 3.5vw, 56px);
}
/* The frame with the steps under it, and no wider than they are. It used to
   take whatever the panel left, which is not the same thing: the widest line
   under the frame is THE STUDIO at about 334px, so filling the column left a
   fifth of it blank off the right-hand edge of everything - the group looked
   pushed against the panel rather than placed.

   Content-width here, fit-content on the body, and .part-body centred in the
   part: the three of them together find the group's real width and put the
   slack on both sides of it. Capped at the cell, so a window narrow enough
   that the title's own width will not fit gets a wrapped title rather than a
   column that overflows.

   min-width so it can be narrower than the frame's content wants - without it
   the 1600px iframe inside sets the floor. */
.part-main {
  flex: 0 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: clamp(20px, 3.2vh, 44px);
}
/* ---- the panel, docked ----
   A strip, not a picture in the flow: it stretches to the part's height and
   the image sits centred in it, sized by whichever of the two runs out first.
   The width is a share of the body rather than the image's own 442px, so a
   narrow column gets a narrow panel instead of a panel and no frame.

   The ink edge and the offset shadow every other box on this page wears, so a
   flat screenshot reads as one of them rather than as an image dropped in. */
.panel-shot {
  flex: 0 1 auto;
  /* 18vw is the 30%-of-the-body it measured at, as a size of its own: the body
     is fit-content now, and a percentage of a width that is the sum of its
     children cannot be one of them. */
  width: clamp(120px, 18vw, 360px);
  margin: 0;
  display: grid;
  place-items: start center;
}
/* The box the picture sits in: the ink edge and the offset shadow, and the
   clip. The chrome is here rather than on the image because the image gets a
   filter when a kit opens, and a filter applies to an element's border and
   shadow as much as to its content - a blurred drop shadow at half opacity is
   a grey cloud. Outside the filter they stay crisp, and the clip keeps the
   blur from bleeding past the edge. */
.shot-box {
  display: block;
  overflow: hidden;
  max-width: 100%;
  /* Something behind the picture. Blurring an image drops its opacity to .45,
     and half of what then shows through is whatever texture the page happens
     to be wearing - so the panel went from a screenshot to a window onto the
     background. Paper, which is what the panel in the picture is standing on
     anyway: the blur now reads as a thing out of focus rather than as a thing
     fading out. It is also what the blur's edges sample into, where before
     they sampled into nothing. */
  background: var(--bg);
  border: 2px solid var(--ink);
  box-shadow: 3px 5px #00000033;
}
/* Sized by the strip's width, with the shape it actually is. No aspect-ratio
   here: the width/height attributes give the box its shape before a byte has
   arrived (an unloaded `width: auto` image is 0 and the layout jumps when it
   lands), and once the file is in, its own proportions take over - so a new
   screenshot of a different shape is never stretched to the old one.

   Not lazy, unlike the hidden figure in the copy column this file came from:
   this one is at the top of the page. */
.panel-shot img {
  display: block;
  width: 100%;
  height: auto;
  transition: filter .25s ease, opacity .25s ease, scale .25s ease;
}
/* ---- out of focus while a kit is open ----
   Two panels on the screen at once, one of them a photograph of the other and
   both at about the same size: the question "why is this one not answering
   the pointer" is a fair one and the page should not make anybody ask it. So
   the picture stands down while a real panel is up - out of focus, colourless
   and half there, which is not a state anything you can use is ever in.

   Set on the root by intro.js, from each kit's own open and close events,
   because either kit can be the one running - see the note by has-kit-open
   there for why it is not a boolean.

   Scaled a hair as well: a blur samples past the element's edges, where there
   is nothing, and the picture would go translucent around its own rim. The
   overflow on the box takes the difference. */
.has-kit-open .panel-shot img {
  filter: blur(5px) grayscale(1);
  opacity: .45;
  scale: 1.05;
}
@media (prefers-reduced-motion: reduce) {
  .panel-shot img { transition: none; }
}

/* The demo buttons, the same way: while a kit is open, the buttons for the
   other two go out of focus like the picture does, so the one still sharp is
   the tool that is running. html[data-kit-open] is set by intro.js.

   Less blur than the picture - a button is small and 5px takes it past
   recognising. And out of reach as well as out of focus: the pointer passes
   straight through, so the running kit has to be closed before another one
   can be opened from here. */
.actions.pair .button { transition: filter .2s ease, opacity .2s ease, background-color .12s ease; }

/* Each button in a box of its own, the way the picture is in .shot-box: the
   box carries the ink ring and the drop shadow and clips what is inside it,
   so a blurred button goes soft inside a hard black edge instead of smearing
   out past it. Paper behind, for the same reason the picture has it - the
   blur and the .45 then read as out of focus, not as see-through.

   The flex share moves to the box, and the button fills it. */
.actions.pair .button-box {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  background: var(--bg);
  box-shadow: 0 0 0 2px var(--ink), 3px 5px #00000033;
}
.actions.pair .button-box > .button { flex: 1 1 auto; box-shadow: none; }
[data-kit-open="texture"] .actions.pair .button:not(.randomize-page),
[data-kit-open="type"] .actions.pair .button:not(.explore-type),
[data-kit-open="move-resize"] .actions.pair .button:not(.explore-move-resize) {
  filter: blur(2.5px) grayscale(1);
  opacity: .45;
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .actions.pair .button { transition: none; }
}
/* The title's own bottom margin, dropped: it was the space between a title and
   the steps under it, and here the column's gap is already that space. Both
   and the title would sit further from the cover it names than the steps sit
   from the cover they follow. Said last-wins over .part-title's margin and the
   wide-screen one below, which both weigh less than this pair of classes. */
.part-main > .part-title { margin-bottom: 0; }
.part-aside { flex: none; }
/* The width is the column's now, not a share of a flex row it no longer sits
   in - and in a column flex a `flex: 1 1 0` would have been a height of 0. */
.stage { width: 100%; min-width: 0; max-width: 900px; }

/* ---- the frame ----
   The studio inside is laid out at its artboard's own size, 1600 x 900, and
   the whole iframe is then scaled down to the frame - so the cover is exactly
   the one the studio draws full size, top menu and all, rather than the studio
   re-laying itself out for a small window (the menu is measured in screen
   pixels, so at 500px wide it came out far too big for the cover around it).

   --frame-scale is the frame's inner width over 1600, kept up to date by
   intro.js; the .5 is only what shows before the script has run. The frame's
   height follows from it: the scaled 900, plus the two 2px rules. */
.frame {
  position: relative;   /* the overlay is laid over the cover */
  width: 100%;          /* the stage around it decides the size */
  height: calc(900px * var(--frame-scale, .5) + 4px);
  overflow: hidden;     /* the unscaled box of the iframe still takes 1600 x 900 */
  margin: 0;
  border: 2px solid var(--ink);
  box-shadow: 3px 5px #00000033;
  /* Black, not paper. Whatever the frame shows between one cover and the next
     - a cover still painting, a lazy iframe before it loads - shows through as
     an unlit screen rather than as a hole in the page. */
  background: #000;
  line-height: 0;
}

/* A preview, not a workspace. Without this a drag lands on a block and leaves
   the cover half-moved, with no way to put it back and nothing saying the
   studio proper is a click away. */
.frame iframe {
  position: absolute;
  top: 0;
  left: 0;
  display: block;
  background: #000;
  width: 1600px;
  height: 900px;
  transform: scale(var(--frame-scale, .5));
  transform-origin: 0 0;
  border: 0;
  pointer-events: none;
  transition: opacity .22s ease;
}

/* The moment of a swap: the cover fades out and the frame it sits in does not,
   so what is left behind is its black. The border can stay exactly as it is -
   it was only ever a problem when the frame went pale underneath it, and a
   black rule around a black picture is one shape, not two. */
.frame.is-swapping iframe { opacity: 0; }

/* The frame as a link: a wash of ink over the cover and the invitation in the
   middle of it. */
.cover {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: #00000000;
  text-decoration: none;
  transition: background-color .18s ease;
}

.cover:hover,
.cover:focus-visible { background: #00000061; }

.cover .label {
  padding: 11px 20px;
  border: 2px solid var(--ink);
  background: var(--bg);
  color: var(--ink);
  font-family: var(--ui-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .12em;
  line-height: 1;
  text-transform: uppercase;
  box-shadow: 3px 5px #00000033;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity .18s ease, transform .18s ease;
}

.cover:hover .label,
.cover:focus-visible .label { opacity: 1; transform: none; }

/* No pointer, no hover: the invitation has to be standing there already. */
@media (hover: none) {
  .cover { background: #0000002e; }
  .cover .label { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .frame iframe, .cover, .cover .label { transition: none; }
}

/* The cycle clock: a short white line near the bottom of the cover that fills
   with ink over one turn of the rotation (intro.js drives it).

   A 60px line rather than the full width of the cover. Edge to edge it was a
   loading bar across a picture, which is the shape of something the page is
   waiting for - and nothing is waiting, the cover is simply due to change.
   Short and centred it reads as a clock for a thing that recurs.

   Two elements, because two things are being drawn: the track is the white
   line, always its full 60px, and the fill is the ink growing across it. The
   animation is on the fill, so the line itself never changes size.

   Hidden until there is a rotation to clock - intro.js gives up before it
   reaches the bar when there is only one scene, so this hides with the pause
   button and for the same reason. */
.cycle-bar {
  position: absolute;
  left: calc(50% - 30px);
  bottom: 10px;
  width: 60px;
  height: 3px;
  background: var(--bg);
  /* A hairline of the cover's own black around it, so the white line reads
     over a pale photograph as well as over a dark one. */
  box-shadow: 0 0 0 1px #00000038;
  overflow: hidden;
  pointer-events: none;
}
.cycle-bar i {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: left center;
  will-change: transform;
}

/* The cycle control. Auto-advancing content needs a way to stop it that does
   not depend on holding a pointer still, and this is also the way back after
   the rotation has been stopped. In the frame's bottom right corner, just
   clear of the clock bar and over the cover link (so its click is its own -
   see intro.js), a paper mark on a dark chip so it holds on any cover. The
   mark follows aria-pressed: pressed, meaning held, shows the way to start it
   again. */
.cycle-btn {
  position: absolute;
  bottom: 12px;         /* the 4px bar, and the same 8px of air as the side */
  right: 8px;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  padding: 5px;
  border: 0;
  background: #000000a6;
  color: var(--bg);
  cursor: pointer;
}
.cycle-btn[hidden] { display: none; }

.cycle-btn::before {
  content: "";
  width: 100%;
  height: 100%;
  background: currentColor;
  -webkit-mask: var(--icon-pause) center / contain no-repeat;
  mask: var(--icon-pause) center / contain no-repeat;
}

.cycle-btn[aria-pressed="true"]::before {
  -webkit-mask-image: var(--icon-play);
  mask-image: var(--icon-play);
}

.cycle-btn:hover { background: var(--ink); }

/* Open studio, under the steps: the page's primary button, with an arrow that
   steps forward on hover - it leaves for a new tab. */
.part-studio .actions { margin-top: 1.5rem; }
/* Ink, with the label and the arrow in paper. It is the one thing this half of
   the page is asking anyone to do, and it was set quiet - lighter than the
   Randomize button below it, which is the demo rather than the way in.
   Heavier and a size up from the other buttons, and the arrow larger still.

   The white rule inside the ink edge is the Randomize button's, and it does
   the same work here: it holds the button's shape against a textured band,
   where a black edge on a black face has nothing to sit against. Drawn as a
   paper border with the ink ring painted outside it by the first shadow, so
   the button keeps the offset shadow it shares with every other box here. */
.button.open-studio {
  font-size: 14px;
  font-weight: 700;
  border-color: var(--bg);
  box-shadow: 0 0 0 2px var(--ink), 3px 5px #00000033;
  transition: background-color .12s ease;
}
/* A shade up from ink under the pointer, the answer the quiet version gave a
   shade down from paper. Not an invert: the white rule is the shape of the
   button, and a paper face would take it away at the moment of the press. */
.button.open-studio:hover { background: #4d4d4d; }
.open-studio .arrow {
  display: inline-block; margin-left: .4em;
  font-size: 1.45em; line-height: 0; vertical-align: -.08em;
  transition: transform .15s ease;
}
.open-studio:hover .arrow { transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) { .open-studio .arrow { transition: none; } }

/* ============================ narrow ============================
   One column, the studio first: on a phone the frame is the fastest way to say
   what this is, and the copy reads underneath it. */
@media (max-width: 999px) {
  .showcase { order: -1; }
  .part-studio { padding-top: clamp(32px, 6vw, 56px); }
  .copy > section:first-child { padding-top: clamp(32px, 5vw, 56px); }
  /* No room for the panel beside the frame: under everything instead, at its
     own size and centred, so a picture of a docked panel is not stretched to
     the width of a phone to prove it used to be docked. */
  .part-studio .part-body { flex-direction: column; align-items: stretch; }
  .part-main { gap: clamp(20px, 5vw, 40px); }
  .panel-shot {
    order: 1;
    align-self: center;
    width: min(100%, 280px);
  }
  /* The plate took its grid placement from a wrapper that held two of them;
     with one there is no grid to place it in on a single-column page. */
  .part-use .plate { grid-column: auto; grid-row: auto; }
  .part-number { font-size: clamp(4.5rem, 22vw, 8rem); }
}

/* ============================ the repeated parts ============================
   The right column's two parts, said again in the reading column. Mostly this
   is a matter of telling the column to stop: .copy styles every ul and every
   li it contains, and both of those rules land on lists that came with a
   layout and a bullet of their own.

   Everything here is an override, not a new look. The parts keep the type they
   have in the plate - mono, small, the numbered chips - because that is what
   they are; what changes is only the size, which follows this column rather
   than half a screen. */

/* The steps: a block list, not the plate's centred grid, and at the column's
   own size rather than the size a half-screen part sets. */
.copy > .aside-part .steps {
  display: block;
  margin: .6rem 0 0;
}
.copy > .aside-part .steps > li {
  position: relative;
  margin-bottom: .5rem;
  padding-left: 2.1em;
  font-family: var(--ui-mono);
  font-size: .8rem;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: normal;
}
.copy > .aside-part .steps > li:last-child { margin-bottom: 0; }
/* No square: these are numbered, and .copy li::before would give every step a
   bullet as well as a chip. */
.copy > .aside-part .steps > li::before { content: none; }

/* The kit list keeps its own grid and its own square. `.copy ul` is a flex
   column and outweighs `.kits` on its own, so the layout has to be said again
   here where it can win - the margin included, or `.copy ul` would set that
   too.

   Air above and below it. The three kits are a list inside step one, and set
   tight against the step above and the step below they read as four things in
   a row rather than as three things belonging to one of them. */
.copy > .aside-part .kits {
  display: grid;
  gap: .35em;
  margin: .9em 0;
  padding: 0 0 0 .9em;
}

/* The two tool buttons, one to a line, closer together than the 1.5rem a
   part's actions row takes - this column is read, not filled. */
.copy > .aside-part .actions { margin-top: 1.1rem; }

/* ---- two tools, one line ----
   The pair of kit buttons, side by side in a column about 310px wide. At the
   size the other buttons here are set they need something over 400px between
   them, so this row sets its own: a step down in type, tighter tracking and
   half the side padding.

   The row is filled: each button starts at its own label's width and the
   space left over is shared out equally between them (flex-grow 1 on an auto
   basis), so the three end the line together without becoming three equal
   slabs. They can still give way - flex-shrink stays on - so a narrow column
   squeezes them rather than pushing one out of the row.

   The labels wrap rather than hold one line. Held to one line they fit this
   window and clip in a narrower one, which is the worst of the three
   outcomes: the request was for the two BUTTONS to share a line, and a label
   that takes two lines to do that is still one button beside another. Both
   halves are the same width, so a wrap makes the pair taller and never
   lopsided. */
.actions.pair { flex-wrap: nowrap; gap: 16px; }
.actions.pair .button {
  flex: 1 1 auto;
  min-width: 0;
  padding: 9px 6px;
  /* A size up and bold: at 10px and 500 the mono labels went grey on the ink
     and read as a texture rather than as words. */
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .08em;
  line-height: 1.3;
  text-align: center;
}

/* The Demo part's one line of text. It was a bulleted list of one item in the
   plate and a bare span in the column - neither of which is a line of text -
   and the span in particular took whatever its parent happened to set, which
   in the plate is the body's own 16px and the largest type in the box.

   A paragraph, set in the same mono at the same size as the text around it in
   each place. The two places differ because their surroundings do: the
   column's lists are .8rem and the plate's steps .875rem, and this line
   belongs with whichever it is standing next to. */
.copy > .aside-part .lede,
.part-use .plate .lede {
  margin: 0;
  font-family: var(--ui-mono);
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: normal;
}
.copy > .aside-part .lede { font-size: .8rem; }
.part-use .plate .lede { font-size: .875rem; }
