/* The builder's output.
   Every color and measurement comes from the theme variables, so a page built
   here inherits the site's palette rather than carrying its own. */

.ds-row{ width:100% }
/* Three separate measurements, three separate variables.

   The gap between one column and the next is Between columns. The space
   outside the columns, holding the page off the edge of the screen, is the
   page's Gutter. They were one variable, so closing the space between two
   cards meant dragging the whole page inwards.

   The space under a stacked element is a third thing again, and lives on
   .ds-el rather than here. */
.ds-row-in{ display:grid; grid-template-columns:repeat(12,1fr);
  /* Space between columns, and only between them.
   *
   * This was a margin on every column, which put the figure on the outside of
   * the first and last ones as well - so the content of a row no longer lined
   * up with the heading above it, and three separate rules had to subtract it
   * back out of the row's gutter to hide that. A grid gap is internal by
   * definition: it exists between tracks and cannot appear at the ends, which
   * is what the setting was always for. */
  column-gap:var(--ds-col-mx,0px);
  row-gap:var(--ds-el-gap,20px);
  width:100%; max-width:var(--ds-max,1200px); margin-inline:auto;
  /* The column margin taken back off the outside.
   *
   * Every column carries its own left and right margin, which is what puts
   * space between two of them. On the first and last column that margin lands
   * on the outside of the row, where it stacks on top of the page gutter - so
   * a gutter of 50 with a column margin of 20 started the content at 70 and
   * nothing on the page was flush with anything else.
   *
   * The same answer WPBakery has always used: the gutter is the space the
   * columns live inside, so the row gives back exactly what the columns take.
   * Never negative - a column margin wider than the gutter would otherwise
   * pull the content off the side of the page. */
  padding-inline:var(--ds-gutter,24px) }

/* No margin of its own.
   The space between columns is the row's column-gap. A margin puts the figure
   on the outside of the first and last column too, which is exactly what it
   should not do - the setting is for the space between them. */
.ds-col{ grid-column:span 12; min-width:0 }
/* Every width a split can name, not just the ones that were needed the day
   this was written. Six of the twelve existed, so any split using a 2 or a 10 -
   six even columns, a narrow rail, three weighted - had no width rule at all
   and its columns fell back to one track each. */
.ds-col-12{ grid-column:span 12 }
.ds-col-11{ grid-column:span 11 }
.ds-col-10{ grid-column:span 10 }
.ds-col-9 { grid-column:span 9 }
.ds-col-8 { grid-column:span 8 }
.ds-col-7 { grid-column:span 7 }
.ds-col-6 { grid-column:span 6 }
.ds-col-5 { grid-column:span 5 }
.ds-col-4 { grid-column:span 4 }
.ds-col-3 { grid-column:span 3 }
.ds-col-2 { grid-column:span 2 }
.ds-col-1 { grid-column:span 1 }

/* One breakpoint, and everything becomes a single column. Two-thirds of a
   phone screen is not a column, it is a line of two words. */


/* Row widths.
   normal - the content width from Theme Settings, centered
   full   - the whole viewport, keeping the page's side gutter
   bleed  - the whole viewport, no gutter, right to the edges */
.ds-row-normal > .ds-row-in{ max-width:var(--ds-max,1200px); margin-inline:auto;
  padding-inline:var(--ds-gutter,24px) }

/* The gutter, once.
 *
 * A normal row inside the page container is inside that container's gutter
 * already, and adding its own put every row one gutter further in than the
 * page title above it - three left edges on a page that has one. A row still
 * carries the gutter where there is no container to give it one, which is what
 * a full width row climbing out to the viewport needs. */
.ds-shell .ds-row-normal > .ds-row-in{ padding-inline:0 }

/* The same thing again on a phone, where the row itself is given the gutter.
 *
 * Under 640px the theme writes the phone gutter onto every .ds-row, because a
 * desktop figure set on the row would be half the screen on a phone. That is
 * the row carrying the gutter - so the inner grid must not carry it too, or
 * the content sits two gutters in and no row lines up with the heading above
 * it. Above this width nothing changes: the row has no padding of its own
 * there and the inner one is still the only gutter.
 *
 * Written with the row's own class as well so it outweighs the base rule
 * whatever order the stylesheets happen to load in. */
@media (max-width: 640px){
  .ds-row.ds-row-normal > .ds-row-in{ padding-inline:0 }
}

/* Full width means the viewport, not the container the theme happens to put
   the content in - so the row has to climb back out of it. The negative
   margin is measured from the row's own center, which lands on the viewport
   edges whatever the container width is.
   overflow-x:clip on the body, not hidden: clip stops the sideways scrollbar
   that 100vw causes when a vertical scrollbar is present, without turning the
   body into a scroll container and breaking every sticky header on the site. */
/* On the root as well as the body.
   Clipping only the body left the scroll on the document element, so a page
   with a full width row still slid about 8px sideways - the difference
   between 100vw and the width left once the vertical scrollbar has taken
   its 15. Measured rather than assumed: scrollTo(400,y) came back at 7.6.
   clip rather than hidden on both, so neither becomes a scroll container and
   position:sticky keeps working. */
html:has(.ds-row-full),html:has(.ds-row-bleed),html:has(.ds-row-band),
body:has(.ds-row-full),body:has(.ds-row-bleed),body:has(.ds-row-band){ overflow-x:clip }

/* A band is a full width row holding a normal one.
 * The row climbs out to the viewport so whatever is painted on it - a
 * color, a picture, a gradient - reaches both edges of the window. What
 * is inside goes back to the page width and centers, so the words line up
 * with every other row on the page while the background does not.
 *
 * The inner rule is the same one a normal row uses, deliberately: two
 * ideas of "page width" would drift apart the first time --ds-max moved. */
/* The scrollbar taken off, so a full width row is the width you can see.
 *
 * 100vw counts the scrollbar, so with a classic one the row was 15px wider
 * than the page and, being centred, hung 7.5px off each side. overflow-x:clip
 * hides the overhang, so it looked right - but everything measured from the
 * row's own left edge was 7.5px adrift of the page. That is why a slideshow
 * layer pinned to the page margin landed at 43 while the logo sat at 50.
 *
 * --ds-sbw is what the scrollbar costs, published by the builder and nought on
 * an overlay scrollbar, so a phone is unchanged. */
.ds-row-full,.ds-row-bleed,.ds-row-band{
  width:calc(100vw - var(--ds-sbw,0px));
  max-width:calc(100vw - var(--ds-sbw,0px));
  margin-inline:calc(50% - 50vw + var(--ds-sbw,0px) / 2) }

.ds-row-band > .ds-row-in{ max-width:var(--ds-max,1200px); margin-inline:auto;
  padding-inline:var(--ds-gutter,24px) }
.ds-row-full > .ds-row-in{ max-width:none;
  padding-inline:var(--ds-gutter,24px) }
/* A bleed row touches the sides, and now needs nothing to make it.
   The columns carry no margin, so there is none to pull back off - the row is
   simply full width with no gutter. This used to widen the track by two column
   margins and shift it left by one to cancel them. */
.ds-row-bleed > .ds-row-in{ max-width:none; padding-inline:0 }

/* The air inside every row, above and below what it holds.
 *
 * Nought unless the site says otherwise, which is what it always was: space
 * between sections is a gap between rows rather than padding inside them, so
 * the first row starts where the page's top margin says it does and a row's own
 * background is exactly the row.
 *
 * Set on General, Layout, Rows, and overridden per row from the builder's Space
 * tab - an inline padding outranks this, so the site figure is a default rather
 * than a rule and a row that wants its own simply says so. */
.ds-row{
  padding-top: var(--ds-row-pt, 0px);
  padding-bottom: var(--ds-row-pb, 0px);
}

/* A row with a shader behind it. isolation so the canvas cannot slide under a
   neighboring row's background, and the content is lifted above it rather
   than the canvas being pushed down - a negative z-index would put it behind
   the row's own background color as well. */
.ds-row-gl-on{ position:relative; isolation:isolate }
/* Above the background media, below the content.
   A transition shader is a sheet that melts away to reveal what the row has
   behind it, so it has to sit over that. At the same z-index as .ds-row-bg it
   lost, because the media layer comes later in the markup - the shader ran,
   drew every frame, and was painted over by the picture it was supposed to be
   revealing. */
.ds-row-gl{ position:absolute; inset:0; display:block;
  width:100%; height:100%; z-index:1; pointer-events:none }
.ds-row-gl-on > .ds-row-in{ position:relative; z-index:2 }
/* A row's own margins.

   Bottom is on every row, the last one included - rows stack, so this is
   also the distance from one row to the next and there is no second
   setting for that.

   Left and right are not given to a full width or a bleed row: those two
   climb out of the container with a negative margin of their own, and
   being full width is the entire point of them. */
.ds-row{ margin-bottom:var(--ds-row-mb,20px) }
.ds-row:not(.ds-row-full):not(.ds-row-bleed):not(.ds-row-band){ margin-inline:var(--ds-row-mx,0px) }

/* The last row on a page carries no space below it.
 *
 * Row spacing is the distance between one row and the next, and after the last
 * row there is no next - there is the footer, which brings its own padding.
 *
 * Left on, that margin does not even stay inside the page. Neither .ds-page nor
 * .ds-main establishes a block formatting context, so it collapses straight out
 * through both and lands between the main element and the footer, where nothing
 * paints it: measured on the live home page as a 51.82px band of bare html
 * showing between content ending at 4074.5 and a footer starting at 4126.3. On
 * a light site with a dark footer that reads as a white stripe above the
 * footer, which is what it was reported as.
 *
 * The footer's own -25px margin-top was an earlier attempt at the same gap,
 * pulling it up by a fixed amount that could never match a margin the page
 * sets. Removing the margin at its source is what actually closes it. */
.ds-page > .ds-row:last-child{ margin-bottom:0 }

/* A row that arrives over the one before it.
   The clip is written by rowenter.js from the scroll; this only has to make
   sure the row is its own layer and sits above what it is covering. Without
   JavaScript the row is simply whole, which is the right thing to fall back to. */
.ds-enter{ position:relative; z-index:1; will-change:clip-path }

/* A row that fills the window.
   svh rather than vh: on a phone vh is the height with the browser chrome
   retracted, so a "full screen" row is taller than the screen and pushes the
   next one into view the moment the address bar reappears.
   The content centers itself, because a hero that fills the window and then
   sits its words at the top is not what anybody means by full screen. */
.ds-row-screen{ min-height:100svh; display:flex; flex-direction:column }
.ds-row-screen > .ds-row-in{ width:100% }

/* A screen row whose columns fill the screen with it.
 *
 * The row was the full height and the grid inside it was not: .ds-row-in is a
 * grid with flex 0 1 auto, so it sat at the height of its tallest column and
 * the row held a band of empty space under it. A full screen row that puts its
 * content in a strip across the middle is not what anybody means by full
 * screen.
 *
 * One track, filling what is left after anything above it - so the columns are
 * the height of the screen and divide it the way a bento divides its box.
 *
 * Only where the row asked for stretched columns, which is a setting that
 * already exists. A row set to top or middle still puts its columns where it
 * always did. */
.ds-row-screen.ds-cols-stretch:not(.ds-row-swiss) > .ds-row-in{
  /* Basis nought, not auto.
   *
   * A fraction resolves against a definite height and an auto sized flex item
   * has none - the grid measured its own content, the content measured the
   * tracks, and 1fr fell back to auto. Three square pictures came out 220 tall
   * each in a row that then grew to 711 rather than dividing the 515 it had.
   * A basis of nought makes the row's height the grid's height, and the
   * fractions have something to be a fraction of.
   *
   * The floor is min-content rather than nought. Nought let the grid be
   * squeezed to whatever the row's own height was, so content taller than a
   * screen simply hung out of the bottom - clipped while the row still clipped,
   * and hanging over the next row once it stopped. min-content is the height
   * the grid actually needs: below it the flex item will not go, so a row whose
   * content wants more than a screen grows to hold it, and one whose content
   * wants less still takes the whole screen and divides it. Both halves of
   * "adapt to the viewport and the content", from one word. */
  flex: 1 1 0;
  min-height: min-content;
  /* auto-rows, not template-rows.
   *
   * A plain split is one row and a template of one track would have done. A
   * Swiss layout is not: its tiles are placed on rows two and three by hand,
   * and those tracks are implicit - so a single explicit track left them sized
   * to their content and a staggered row came out 34px, 220px, 220px inside a
   * 525px screen. Sizing the implicit tracks covers both, because the one row
   * a plain split uses is implicit as well.
   *
   * This overrides the bento unit deliberately: on a row asked to be a screen
   * with stretched columns, dividing the screen is the instruction.
   *
   * The Swiss shapes are excluded, and that is a design decision rather than a
   * limitation. They are compositions with deliberate voids and tiles shaped by
   * what is in them - dividing a fixed height between their tracks turned a
   * tall tile into a bar and squeezed a text column until its heading clipped.
   * A screen tall Swiss row still fills the screen; its composition simply sits
   * inside that at the size it was drawn to be. */
  /* Never smaller than what is in the row.
   *
   * This was minmax(0, 1fr): the screen divided into equal tracks whatever the
   * tiles held, so a panel with four lines in a 146px track put the last two
   * lines over the tile beneath it. Nothing was clipping - every box reads
   * overflow:visible - the row simply was not tall enough and the words went
   * on regardless.
   *
   * min-content as the floor keeps the fractions doing their work while any
   * row that needs more takes it, and .ds-row-screen is min-height:100svh, so
   * the row grows past the screen rather than losing a word. */
  grid-auto-rows: minmax(min-content, 1fr);
}

/* Descendant, not child.
 *
 * A layout that puts a grid inside half of a row has its pictures two levels
 * down, and a rule that only reached the first level left them at their own
 * height inside a column that was filling the screen. */
.ds-row-screen.ds-cols-stretch:not(.ds-row-swiss) .ds-row-in > .ds-col{
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* The row's own vertical alignment, carried on to the column.
 *
 * A column was a block and its content sat where the row's justify-content put
 * it. Making it a flex column to let the media stretch took that away, so every
 * screen row set to middle drew its words at the top with a screen of white
 * under them. The row's three answers are repeated here because the box that
 * has to answer them has changed. */
.ds-row-screen.ds-cols-stretch:not(.ds-row-swiss).ds-row-vtop .ds-row-in > .ds-col{ justify-content: flex-start }

.ds-row-screen.ds-cols-stretch:not(.ds-row-swiss).ds-row-vmiddle .ds-row-in > .ds-col{ justify-content: center }

.ds-row-screen.ds-cols-stretch:not(.ds-row-swiss).ds-row-vbottom .ds-row-in > .ds-col{ justify-content: flex-end }

/* Except where the column holds something that stretches. A picture told to
   take the room the words do not has already decided where everything sits,
   and centring on top of it puts equal air above and below a block that is
   already the full height. */
.ds-row-screen.ds-cols-stretch:not(.ds-row-swiss) .ds-row-in > .ds-col:has(> .ds-figure),
.ds-row-screen.ds-cols-stretch:not(.ds-row-swiss) .ds-row-in > .ds-col:has(> .ds-row){ justify-content: flex-start }

/* A nested row divides what its own column was given. */
.ds-row-screen.ds-cols-stretch .ds-col .ds-row > .ds-row-in{
  flex: 1 1 0;
  min-height: 0;
  /* Never smaller than what is in the row.
   *
   * This was minmax(0, 1fr): the screen divided into equal tracks whatever the
   * tiles held, so a panel with four lines in a 146px track put the last two
   * lines over the tile beneath it. Nothing was clipping - every box reads
   * overflow:visible - the row simply was not tall enough and the words went
   * on regardless.
   *
   * min-content as the floor keeps the fractions doing their work while any
   * row that needs more takes it, and .ds-row-screen is min-height:100svh, so
   * the row grows past the screen rather than losing a word. */
  grid-auto-rows: minmax(min-content, 1fr);
}

/* And a picture in one takes the room the words do not.
 *
 * This is the whole of what makes a bento read as a bento: the type is the
 * size it is, and the media stretches to whatever is left. Without it a column
 * fills the screen and its picture sits at the top of it. */
/* A nested row is .ds-inrow, not .ds-row - a row written as an element rather
   than as a row of the page. Naming the wrong class here is why a column split
   into sections still stacked them at its head. */
.ds-row-screen.ds-cols-stretch:not(.ds-row-swiss) .ds-row-in > .ds-col > .ds-figure,
.ds-row-screen.ds-cols-stretch:not(.ds-row-swiss) .ds-row-in > .ds-col > .ds-inrow,
.ds-row-screen.ds-cols-stretch:not(.ds-row-swiss) .ds-row-in > .ds-col > .ds-col-in > .ds-figure,
.ds-row-screen.ds-cols-stretch:not(.ds-row-swiss) .ds-row-in > .ds-col > .ds-col-in > .ds-inrow{
  flex: 1 1 auto;
  min-height: 0;
}

.ds-row-screen.ds-cols-stretch:not(.ds-row-swiss) .ds-row-in > .ds-col > .ds-figure img{
  width: 100%;
  height: 100%;
  object-fit: var(--ds-img-fit, cover);
}

/* The page margin is for content, not for a picture that is meant to reach the
   top of the window. A full screen first row was starting the page margin below
   the header, so a hero began with a band of page background above it. */
/* The row is wrapped in the post article, so both depths are matched - a
   child combinator alone silently misses every real page. */
/* Not when there is a title band above it. The margin is what separates the
   page from the band, so dropping it on a page with one puts a full screen row
   flush against the title - which reads as the row sitting on top of it. The
   band is the top of the page there, and this rule is only about a hero that is
   meant to be. */
/* The hero's own top margin is dropped in the theme, by zeroing --ds-main-pt
   where the first row is a screen row - and what sits above it after that is
   the masthead, or nothing at all where the header overlays. Zeroing the
   padding outright put an opaque header on top of the first eighty pixels of
   the hero. */

/* An inline header takes its own height out of the window, so a first row
   asking for the whole window is taller than what is left and the bottom of it
   can never be on screen at once. Only the first row: a full screen row further
   down the page has the whole window to itself. */


/* And the top bar above it.

   Its own rule rather than another term in the one above, because the two
   are not the same question: a transparent header takes no height out of the
   window and the bar over it takes its height whatever the header is doing.
   Only the rule for a bar applied, and only when the header was inline, so a
   hero on a site with a bar was always the bar's height too tall and its
   bottom edge could never be on screen.

   The border counts. It is a real line under a real strip, and at three or
   four pixels it is the difference between the row ending at the fold and
   the page offering a scrollbar for nothing. */




/* What is really above it, rather than the sum of the things that might be.
 *
 * Every rule above names the parts and takes them off the window, which is only
 * right while those parts are stacked above the row. They are not always: the
 * page margin is dropped for a hero, so the row starts at the very top of the
 * window and the sticky header is drawn over it - and the arithmetic then took
 * a header and a bar out of a row that had the whole window, leaving it short
 * by about their height with the next row showing underneath.
 *
 * --ds-screenrow-top is the measured distance from the top of the document to
 * the row's own top, published by bar-heights.js. It is the same answer in
 * every arrangement because it is measured rather than assumed, so it is last
 * and it wins. Falls back to the rules above where the script has not run. */
/* A full screen row is the height of the window.
 *
 * Except the first one on a page, which has the masthead and the page's own top
 * margin above it - so it is the window less those, and the two together come to
 * exactly one screen.
 *
 * Both figures are already known: --ds-header-space is what the content leaves
 * for the masthead, bar included, and --ds-main-pt is the page margin. Nothing
 * is measured, so nothing depends on where the page happens to be scrolled.
 *
 * It used to subtract --ds-screenrow-top, a distance bar-heights.js measured
 * from the top of the document as rect.top + window.scrollY. ScrollSmoother moves
 * the content with a transform, and window.scrollY is not that transform - so the
 * figure was only right while the page sat at the very top, and a hero sized
 * anywhere else came out over or under the window. A row's height should not
 * depend on where the reader has scrolled to. */
.ds-row-screen{ min-height:var(--ds-screen, 100svh) }

/* --ds-header-space is the wrong figure to take off here, and it is wrong in
 * both directions.
 *
 * It is written by the theme as 0px when the masthead is in the flow, and as
 * the masthead's full height when it overlays. So subtracting it did nothing
 * in the one case that needs a subtraction, and took a masthead's worth off
 * the one case that must not have one - a hero under a transparent header
 * came out short by the height of the header and its bar, and the row that
 * was meant to fill the window ended two thirds of the way down it.
 *
 * What actually sits above the row is what should come off:
 *   overlaying header - nothing sits above it, so nothing comes off
 *   header in the flow - the bar and the masthead do, so they come off
 * The page's own top margin comes off either way. */
/* How much screen a screen row actually has.
 *
 * Named once and used everywhere, because a row that says min-height one way
 * and height another is a row with two answers - and the fixed height wins,
 * which is how the first row came out a full screen tall with a masthead
 * stacked above it and ran a header's worth off the bottom of the window. */
.ds-row-screen{ --ds-screen: 100svh }

.ds-main > .ds-row:first-child.ds-row-screen,
.ds-main > :first-child > .ds-row:first-child.ds-row-screen{
  --ds-screen: calc(100svh - var(--ds-main-pt, 48px));
  min-height: var(--ds-screen);
}

body.ds-header-inline .ds-main > .ds-row:first-child.ds-row-screen,
body.ds-header-inline .ds-main > :first-child > .ds-row:first-child.ds-row-screen{
  /* Exactly what sits above the row, each counted once.
   *
   * --ds-header-h is the masthead's whole rendered height, bar and padding
   * included, so adding those separately took the bar off twice and left the
   * row short of the window by a bar. And the page's top padding is main-pt
   * and header-space together, the way the theme writes it, so leaving the
   * second out hung the row off the bottom by that much instead. Measured
   * against the row's real offset at seven window sizes rather than reasoned
   * from the names. */
  --ds-screen: calc(100svh
    - var(--ds-header-h, 80px)
    - var(--ds-header-space, 0px)
    - var(--ds-main-pt, 48px));
  min-height: var(--ds-screen);
}

/* A masthead that floats takes no room in the flow.
 *
 * Sticky, it is drawn over the page instead of above it: the page's own top
 * margin goes to nought and --ds-header-space becomes the masthead's full
 * height, so that one figure is the whole of what sits over the row. Counting
 * the masthead again on top of it took its height off twice and left the first
 * row a header short of the window with white under it.
 *
 * After the rule above and at the same weight, so a floating header wins on
 * order - which is what "floating" means here. */
body.ds-header-sticky .ds-main > .ds-row:first-child.ds-row-screen,
body.ds-header-sticky .ds-main > :first-child > .ds-row:first-child.ds-row-screen{
  --ds-screen: calc(100svh
    - var(--ds-header-space, 0px)
    - var(--ds-main-pt, 48px));
  min-height: var(--ds-screen);
}

/* Where the content sits in a row taller than its content. Only means anything
   on a full screen row, which is the only kind with room to spare. */
.ds-row-screen.ds-row-vtop   { justify-content:flex-start }
.ds-row-screen.ds-row-vmiddle{ justify-content:center }
.ds-row-screen.ds-row-vbottom{ justify-content:flex-end }

/* While a row is pinned, its words stay in the middle of the window.
   A pinned row is held still and its effect plays across it, so text sitting at
   the top of the row ends up wherever the pin happened to freeze it - usually
   half off the screen. Anchoring to the viewport rather than to the row keeps
   the label readable for the whole time the effect is running. */
.ds-row-screen.ds-pinned > .ds-row-in{
  position:sticky; top:50%; transform:translateY(-50%) }

/* ---- one row arriving over another ----
   The overlap is layout rather than something re-created every frame. The row
   being covered sticks to the top of the window; the row arriving is drawn one
   row higher so it lies over it. Neither leaves the flow, so nothing below
   moves and nothing collapses when the effect finishes.
   The pull-up itself is written inline by rowenter.js from the measured height
   of the row being covered - a row carries its own margin as an inline style,
   so a rule here would lose to it every time. */
/* Nothing sticks and nothing is pulled up any more - a row is revealed inside
   its own space. Sticking the previous row and pulling this one over it took a
   row's height out of the document, so the page came up short and the last row
   ran under the footer. */
.ds-enter{ position:relative; z-index:1 }

/* And how the columns line up against each other. Stretch is the grid default,
   so it is written as the absence of the others rather than as a rule. */
.ds-cols-top    > .ds-row-in{ align-items:start }
.ds-cols-middle > .ds-row-in{ align-items:center }
.ds-cols-bottom > .ds-row-in{ align-items:end }

/* A first row under a transparent header starts beneath it, so the header sits
   over the row rather than above it - which is the whole point of transparent.
   Only the first row, and only when the header is actually transparent. */
body.ds-header-transparent .ds-row:first-child.ds-row-screen{ margin-top:0 }

/* Backgrounds that are not a flat color.
   The picture and the video share one layer class, so a fade, a dim and the
   stacking order are written once and cannot disagree between the two. */
/* No overflow:hidden here.
 *
 * This clipped the row so the background layer could not escape it, and took
 * the row's content with it: a full screen row with more in it than a screen
 * grew taller, correctly, and then had the overflow cut off at the fold.
 * Clipping is never the right answer to content that does not fit - it
 * destroys the content and says nothing to whoever built the page.
 *
 * Nothing needs the row to clip. .ds-row-bg is pinned to all four sides at
 * exactly the row's size, so it has never had anything to spill. The one layer
 * that does overhang is the drifting one below, which is deliberately taller
 * than the row, and that is the only case the clip is kept for. */
.ds-row-hasmedia{ position:relative; isolation:isolate }
.ds-row-hasmedia:has(> .ds-row-bg-para){ overflow:hidden }

/* The window a drifting layer travels in.
 *
 * The row cannot be the window on a full screen row - the rule below keeps that
 * one visible so content is never cut - and without one the overhang crosses
 * into the next row. Measured on the studio bands: 114px of overhang against a
 * 113.8px gap, so the picture filled the space between two rows and they read as
 * touching. Pinned to the row's four sides, so it is the row's own rectangle and
 * nothing about the layer's geometry or the script's arithmetic changes. */
.ds-row-hasmedia > .ds-row-bgclip{
  position:absolute; inset:0; overflow:hidden;
  z-index:0; pointer-events:none;
}
/* Except on a full screen row, which is the one place content most often has
   more to say than a screen holds. Content wins over the drifting layer's edge
   there: at the extremes of its travel that edge can come into view, which is a
   background moving a little too far. The alternative is a cut paragraph. */
.ds-row-screen.ds-row-hasmedia{ overflow:visible }
.ds-row-bg{ position:absolute; inset:0; width:100%; height:100%;
  z-index:0; pointer-events:none }

/* A drifting layer needs somewhere to drift.
 *
 * .ds-row-bg is pinned to all four sides, so moving it would pull its own edge
 * into the row and show whatever is behind. Taller than the row and lifted by
 * half the difference, it has the same amount of travel above and below and the
 * row stays covered at either end of it.
 *
 * The figures are not a guess. The script moves the layer by a quarter of the
 * row's height over the whole time the row is on screen, so it travels an
 * eighth of that height each way - 12.5%. The overhang has to be at least that
 * or the layer's own edge comes into the row at the extremes: measured on a
 * 141px row, travel was +/-17.6px against the 11.2px an 8% overhang gave, and
 * the row showed through. 14% each side covers it with a little to spare. */
.ds-row-bg-para{
  top:-14%; height:128%;
  will-change:transform;
  transform:translate3d(0, var(--ds-rowbg-y, 0px), 0);
}

/* Nothing moves for somebody who asked for no movement. */
@media (prefers-reduced-motion: reduce){
  .ds-row-bg-para{ top:0; height:100%; transform:none }
}

/* A row set to Off on mobile keeps its picture and loses the travel, so the
   overhang has to go with it: a layer 128% tall that is not moving shows the
   row through its own top and bottom edge. */


/* Which part of the film survives the crop.
 *
 * object-fit:cover with nothing else centers it, and centering a standing figure
 * on a wide screen takes the top off - the head goes first, because it is the
 * edge furthest from the middle. The row already asks where a background sits
 * for the picture case; this is that same answer applied to the film. */
/* An icon that keeps moving once it has arrived.
 *
 * CSS rather than script: these never stop, never listen for anything and never
 * need to be told where the page is - which is the one case where a keyframe is
 * simply the right tool. Both figures come off the element as custom properties,
 * so one set of rules serves every icon on the page at whatever pace and
 * distance each was given.
 *
 * The distance is a percentage of the icon's own box, so 20 reads the same on a
 * 24px mark as on a 96px one.
 *
 * Anyone who asked for less movement gets none of it. A mark bouncing forever in
 * the corner of the eye is exactly what that setting is about. */
/* Left as the block it is without an animation on it.
 *
 * This shrank the element to inline-block so it wrapped the mark. The alignment
 * is the element's own text-align, which centers the icon across the column
 * only while the element is as wide as the column - shrunk to the 24px of the
 * mark itself, center and left are the same place, and setting any animation
 * moved a centered icon to the far left of the row. */
.ds-icon-move{ will-change:transform, opacity }

@media (prefers-reduced-motion: reduce){
  .ds-icon-move{ animation:none !important; will-change:auto }
}

@keyframes ds-icon-bounce{
  0%,100%{ transform:translateY(0) }
  50%    { transform:translateY(calc(var(--ds-icon-amt,20%) * -1)) }
}

@keyframes ds-icon-float{
  0%,100%{ transform:translateY(calc(var(--ds-icon-amt,20%) * -0.5)) }
  50%    { transform:translateY(calc(var(--ds-icon-amt,20%) * 0.5)) }
}

@keyframes ds-icon-pulse{
  0%,100%{ transform:scale(1) }
  50%    { transform:scale(1.18) }
}

@keyframes ds-icon-spin{
  from{ transform:rotate(0) }
  to  { transform:rotate(360deg) }
}

@keyframes ds-icon-swing{
  0%,100%{ transform:rotate(calc(var(--ds-icon-amt,20%) / -1.5)) }
  50%    { transform:rotate(calc(var(--ds-icon-amt,20%) / 1.5)) }
}

/* Eased both ways on everything that turns around, so it reads as weight rather
   than as a thing being dragged between two points. The spin is the exception:
   a rotation that eases is a rotation that stutters once a second. */
.ds-icon-bounce{ animation:ds-icon-bounce var(--ds-icon-secs,3s) cubic-bezier(.5,0,.5,1) infinite }
.ds-icon-float { animation:ds-icon-float  var(--ds-icon-secs,3s) ease-in-out infinite }
.ds-icon-pulse { animation:ds-icon-pulse  var(--ds-icon-secs,3s) ease-in-out infinite }
.ds-icon-spin  { animation:ds-icon-spin   var(--ds-icon-secs,3s) linear infinite }
.ds-icon-swing { animation:ds-icon-swing  var(--ds-icon-secs,3s) ease-in-out infinite;
  transform-origin:50% 0 }

/* On their own clock, like the five above.
 *
 * These two were written against the scroll - animation-timeline: view(), and
 * then a script when that turned out to do nothing under a smooth scroller. Both
 * were the wrong idea. An icon animation is set once and is expected to run, and
 * tying it to the scroll meant the mark was only ever whatever the reader's
 * scroll position happened to mean: half faded on a page nobody had moved, and
 * nothing at all in a hero, which is a row that hardly travels. They loop.
 *
 * Drift lifts and fades away, then begins again from where it started. In and
 * out is the same idea from both ends - nothing at the edges, solid through the
 * middle - which is what makes it read as a mark that keeps arriving. */
@keyframes ds-icon-drift{
  0%     { transform:translateY(0); opacity:1 }
  70%,100%{ transform:translateY(calc(var(--ds-icon-amt,20%) * -1)); opacity:0 }
}

/* In at the top and out at the bottom, which is the way it is named and the way
   a mark that points down wants to travel. */
@keyframes ds-icon-pass{
  0%  { opacity:0; transform:translateY(calc(var(--ds-icon-amt,20%) * -1)) }
  35%,65%{ opacity:1; transform:translateY(0) }
  100%{ opacity:0; transform:translateY(var(--ds-icon-amt,20%)) }
}

.ds-icon-drift{ animation:ds-icon-drift var(--ds-icon-secs,3s) ease-in-out infinite }
.ds-icon-pass { animation:ds-icon-pass  var(--ds-icon-secs,3s) ease-in-out infinite }

.ds-row-vid{ object-fit:cover; object-position:var(--ds-row-vpos, center) }
.ds-row-hasmedia > .ds-row-in{ position:relative; z-index:2 }

/* One overlay for both the image and the video case, drawn between the
   background and the content rather than as a second element to position. */
.ds-row-dimmed{ position:relative; isolation:isolate }

/* The wash over the background media.

   Its own layer rather than ::after on the row, so it can carry the same
   fade the picture carries - see the note in the renderer. */
.ds-row-dim{ position:absolute; inset:0; z-index:1;
  background:#000; opacity:var(--ds-row-dim,0); pointer-events:none }
/* Above the wash and above the words behind it - the content is the thing
   being read, and everything else in the row is behind it. */
.ds-row-dimmed > .ds-row-in{ position:relative; z-index:3 }

/* The space between elements is a margin BELOW each one, and there is no top
   margin anywhere.
 *
 * It used to be a top margin on the element that followed, which had two faults.
 * An element carries no space above it by choice - the first element in a column
 * had a margin the designer never asked for the moment anything preceded it. And
 * two vertical margins that meet collapse to the larger of the pair, so an
 * element set to 8px below still sat 20px away from the next thing and one set
 * to 0 still sat 20: the figure was written, emitted, and then lost to its
 * neighbor. Anything larger than the default worked, which is what made it look
 * like it worked sometimes.
 *
 * Below-only has nothing to collapse against, so the element's own margin simply
 * wins - and the marker class the renderer used to add to work around it is gone
 * with the problem.
 *
 * The figure is Theme Settings, General, Element spacing. */
.ds-el{ margin-top:0; margin-bottom:var(--ds-el-gap,50px) }

/* A caption belongs to its picture.
 *
 * Text directly under a Figure is a caption in every case anybody writes one,
 * but it was an element like any other - so a screenshot cost 8px to its
 * caption and then a full element gap under that, and a column of screenshots
 * read as label, air, picture rather than picture, label, air.
 *
 * The pair keeps the small gap between the two and a shorter one after it, so
 * the space falls between pairs where it says something. */
.ds-figure + .ds-text{ margin-top:0; margin-bottom:min(var(--ds-el-gap,50px), 1.6em) }

/* The site's shadow, on an element that asked for one.
   The figure is General, Layout, Box shadows - one shadow for the whole site,
   so an element saying yes sits at the same height as everything else that
   did rather than at a depth of its own. */
.ds-el-shadow{ box-shadow:var(--ds-shadow, none) }

/* Shadow: Off, against a site wide tick that says every card has one.
   Two classes deep so it outranks the one class the global rule uses. */
.ds-el-noshadow,
.ds-el-noshadow .ds-card,
.ds-el-noshadow .ds-post-card,
.ds-el-noshadow .dst-card,
.ds-el-noshadow .ds-box,
.ds-el-noshadow img,
.ds-el-noshadow video{ box-shadow:none }

/* Frosted, and edged in two colors.
 *
 * The blur only shows where the fill lets it: a solid background has nothing
 * behind it to soften, which is why this is a length and not a switch - the
 * color above it is the other half of the decision.
 *
 * The gradient edge is two backgrounds rather than a border color, because a
 * border cannot hold one. The first fills the padding box with whatever the
 * element already sits on; the second fills the border box with the gradient,
 * and the clip between them leaves the gradient showing in the edge alone. */
.ds-box, .ds-card, .ds-post-card{
  backdrop-filter:blur(var(--ds-box-blur, 0));
  -webkit-backdrop-filter:blur(var(--ds-box-blur, 0)) }
.ds-card, .ds-post-card{
  backdrop-filter:blur(var(--ds-card-blur, 0));
  -webkit-backdrop-filter:blur(var(--ds-card-blur, 0)) }

.ds-box:where([style*="--ds-box-grad"]), .ds-box{ border-color:var(--ds-box-line) }

@supports (background-clip: padding-box){
  .ds-box{
    background-image:var(--ds-box-grad, none);
    background-origin:border-box;
    background-clip:padding-box, border-box }
  .ds-card, .ds-post-card{
    background-image:var(--ds-card-grad, none);
    background-origin:border-box;
    background-clip:padding-box, border-box }
}

/* A rule in the flow of the page.
 *
 * The line is a top border rather than a colored box with a height: one
 * property carries both the thickness and the color, and a thickness of zero
 * then draws nothing without needing to be special-cased.
 *
 * The color is the site's divider color, which is the whole reason this
 * element exists - the setting was there with nothing to draw. An element given
 * its own color writes it inline and that wins, which is what "overrides"
 * means here. The last fallback follows the text, faintly, for a site that has
 * never opened the Dividers card. */
.ds-line{
  border-top:1px solid
    var(--ds-rule-color, var(--ds-line, color-mix(in srgb, currentColor 14%, transparent)));
  width:100% }

/* A heading belongs to what comes after it.
 *
 * Every element carries the same gap below itself, which is right between two
 * separate things and wrong under a heading - it left a 50px hole between
 * "Sign in" and the form it names, so the words read as belonging to whatever
 * was above rather than below.
 *
 * Sized against the heading rather than in pixels, so an h2 at 44px gets more
 * room than an h5 at 18px without either being set by hand, and capped at the
 * element gap so it can never exceed the space between two unrelated things.
 * The last element in a column keeps the full gap: there is nothing under it to
 * belong to. */
/* Unless Typography, Performance has been given a figure - that is somebody
   asking for the same gap under every heading, and it has to beat this rule or
   the setting does nothing on any page built here, which is all of them. */
.ds-el.ds-heading:not(:last-child){
  margin-bottom:var(--ds-h-mb, min(var(--ds-el-gap,50px), .55em));
}


/* Blend modes, from the Blend mode field on the text elements.
 *
 * Classes rather than an inline style: the mode is one of a known seven, and
 * seven rules in the stylesheet beat a style attribute on every heading on the
 * page. A blend only reaches as far as its nearest stacking context, so nothing
 * here creates one of its own. */
.ds-blend-multiply  { mix-blend-mode:multiply }
.ds-blend-screen    { mix-blend-mode:screen }
.ds-blend-overlay   { mix-blend-mode:overlay }
.ds-blend-difference{ mix-blend-mode:difference }
.ds-blend-exclusion { mix-blend-mode:exclusion }
.ds-blend-luminosity{ mix-blend-mode:luminosity }


/* The icon box.
 *
 * A grid rather than a flex line, because the icon and the words swap places
 * and change flow direction between the four positions, and a grid says that
 * in one property each time instead of three.
 *
 * It carries .ds-box too, so the fill, the edge, the blur and the border
 * gradient set on Theme Settings, Elements, Box element all reach it. Only
 * what is particular to this element is written here. */
.ds-iconbox{
  display:grid; gap:var(--ds-iconbox-gap,18px);
  align-items:start;
}
/* A whole card that is a link.

   The anchor covers the card rather than containing it - see the note in
   render_iconbox. Above the words so the whole face is clickable, and under
   nothing, because there is nothing in a card to click but the card. */
.ds-iconbox.has-link{ position:relative }
.ds-iconbox-hit{
  position:absolute; inset:0; z-index:2;
  border-radius:inherit; text-decoration:none;
}
.ds-iconbox-hit:focus-visible{
  outline:2px solid var(--ds-field-focus, var(--ds-link));
  outline-offset:2px;
}

.ds-iconbox-mark{ display:flex; line-height:0;
  color:var(--ds-iconbox-ink, var(--ds-icon-ink, currentColor)) }
.ds-iconbox-mark svg{ width:auto; height:auto; display:block }
.ds-iconbox-head{ margin:0 0 .35em }
.ds-iconbox-words > :last-child{ margin-bottom:0 }

/* Above the words, aligned to whichever edge the text is. */
.ds-iconbox.is-top{ grid-template-columns:minmax(0,1fr) }

/* Above and centered, everything under it centered with it. */
.ds-iconbox.is-middle{ grid-template-columns:minmax(0,1fr); justify-items:center; text-align:center }

/* Beside the words. The icon column is as wide as the mark and no wider, so a
   long heading takes the rest of the row rather than being squared off against
   it. */
.ds-iconbox.is-left{ grid-template-columns:auto minmax(0,1fr) }
.ds-iconbox.is-right{ grid-template-columns:minmax(0,1fr) auto }
.ds-iconbox.is-right .ds-iconbox-mark{ order:2 }
.ds-iconbox.is-right{ text-align:right }
.ds-iconbox.is-right .ds-iconbox-mark{ justify-self:end }

/* The three shadows the Box offers, and the site's own. */
.ds-iconbox.ds-boxsh-soft{ box-shadow:0 6px 24px rgba(0,0,0,.28) }
.ds-iconbox.ds-boxsh-deep{ box-shadow:0 16px 48px rgba(0,0,0,.6) }
.ds-iconbox.ds-boxsh-theme{ box-shadow:var(--ds-shadow,none) }

@media (max-width:640px){
  /* Beside becomes above on a phone: an icon and a paragraph side by side in a
     three hundred pixel column leaves the words about eight characters wide. */
  .ds-iconbox.is-left,
  .ds-iconbox.is-right{ grid-template-columns:minmax(0,1fr); text-align:left }
  .ds-iconbox.is-right .ds-iconbox-mark{ order:0; justify-self:start }
}

.ds-align-left  { text-align:left }
.ds-align-center{ text-align:center }
.ds-align-right { text-align:right }

/* Justified, and made to work on one line.
 *
 * text-align:justify on its own deliberately leaves the last line alone, and on
 * a three word heading every line is the last line - so it looks like nothing
 * happened. That is why this never works when it is reached for. text-align-last
 * is the half that spreads that line, and with both, three words sit one at each
 * edge and one in the middle, held there as the column changes width.
 *
 * The two are stated separately rather than relying on the shorthand, because
 * text-align-last is not part of text-align. */
.ds-align-justify{
  text-align: justify;
  -moz-text-align-last: justify;
  text-align-last: justify;
  /* A single word longer than the column has nothing to space against and would
     otherwise push the line wider than the tile. */
  overflow-wrap: break-word;
}

/* A heading is an element like any other and takes the same gap below it.
   Only the top margin is zeroed here, and only because the theme's own h1-h6
   rule carries one - the gap below comes from the element rule above, the same
   as every other element, and an element that sets its own margin beats both. */
.ds-heading{ margin-top:0 }

/* The kicker: one line of type over a heading.
 *
 * Its own size, weight, case and tracking, all from Typography, and its own
 * color from Elements - falling back to the heading color, which is what it
 * looked like before the color existed.
 *
 * The gap below is the heading's, not the element gap: a kicker belongs to the
 * heading under it, and 50px of air between the two reads as two unrelated
 * pieces of text rather than one. */
.ds-kicker{
  font-family:var(--ds-kicker-family, var(--ds-font-heading));
  color:var(--ds-kicker-ink, var(--ds-heading));
  font-size:var(--ds-kicker-size,max(calc(14px * var(--ds-lo, .5333)), 0.7292vw));
  font-weight:var(--ds-kicker-weight,600);
  text-transform:var(--ds-kicker-case,uppercase);
  line-height:var(--ds-kicker-lh,1.3em);
  letter-spacing:var(--ds-kicker-track,.12em);
  margin:0;
}
.ds-el.ds-kicker:not(:last-child){
  margin-bottom:min(var(--ds-el-gap,50px), .8em);
}
.ds-text > :first-child{ margin-top:0 }
.ds-text > :last-child{ margin-bottom:0 }

/* The three body sizes, each set in Typography. Applied to the block and
   inherited by everything inside it, so a list or a blockquote in a large
   paragraph is large too rather than dropping back to the default. Medium falls
   back to the paragraph settings, which is what it was before this existed. */
.ds-text-large{
  font-size:var(--ds-text-large-size,max(calc(22px * var(--ds-lo, .5333)), 1.1458vw));
  font-weight:var(--ds-text-large-weight,400);
  line-height:var(--ds-text-large-lh,1.45em);
  letter-spacing:var(--ds-text-large-track,0em);
  text-transform:var(--ds-text-large-case,none);
}
.ds-text-medium{
  font-size:var(--ds-text-medium-size, var(--ds-p-size,17px));
  font-weight:var(--ds-text-medium-weight, var(--ds-p-weight,400));
  line-height:var(--ds-text-medium-lh, var(--ds-p-lh,1.4em));
  letter-spacing:var(--ds-text-medium-track, var(--ds-p-track,0em));
  text-transform:var(--ds-text-medium-case,none);
}
.ds-text-small{
  font-size:var(--ds-text-small-size,max(calc(14px * var(--ds-lo, .5333)), 0.7292vw));
  font-weight:var(--ds-text-small-weight,400);
  line-height:var(--ds-text-small-lh,1.5em);
  letter-spacing:var(--ds-text-small-track,0em);
  text-transform:var(--ds-text-small-case,none);
}

/* Its own reset, and the element gap under it.
 *
 * margin:0 is a shorthand written after .ds-el, so it took the standard gap
 * with it and every picture in the suite has been sitting flush against the
 * next one. Three elements had the same fault: the list, the code block and
 * this - which is why a grid of pictures came out as one grey slab. */
.ds-figure{ margin:0 0 var(--ds-el-gap,50px) }
.ds-img{ max-width:100%; height:auto; display:block }
.ds-corner-soft { border-radius:8px }
.ds-corner-round{ border-radius:22px }

/* Alignment is text-align on the figure, so an image narrower than its column
   sits where it was asked to. inline-block rather than a margin trick: the
   figure may hold a button or a link, and those have to move with it. */
.ds-figure > *{ display:inline-block; max-width:100% }

/* A drawing fills the box it was given.
 *
 * An SVG carries a width in its own file, and width:auto takes it - so a logo
 * declared at 768px stopped at 768px inside a box that had grown to 1173 on a
 * 4K screen, and it was the one picture on the page that did not keep step.
 * Vector only: a small photograph stretched to fill a wider box is a blurred
 * photograph, and auto is the right answer for that one. */
.ds-figure > img[src$=".svg"]{ width:100%; height:auto }
.ds-align-center .ds-img,.ds-align-right .ds-img{ margin-inline:auto }
.ds-align-right  .ds-img{ margin-right:0 }

/* The clickable picture is a button, and a button brings a look with it. */
.ds-lb-open{ padding:0; border:0; background:none; cursor:pointer; display:block;
  line-height:0; font:inherit; color:inherit }
.ds-lb-open img{ transition:transform .5s cubic-bezier(.22,1,.36,1),
  filter .5s cubic-bezier(.22,1,.36,1) }
.ds-lb-open:hover img{ transform:scale(1.02); filter:brightness(1.06) }
.ds-lb-open:focus-visible{ outline:2px solid currentColor; outline-offset:4px }

/* ---------------------------------------------------------------------
 * Arriving on screen
 *
 * The starting state is CSS, not JavaScript - the picture must be hidden
 * before the script runs or it flashes at full size and then animates in.
 * .is-in is added the moment it is seen, and is also what a browser with no
 * IntersectionObserver gets straight away.
 * ------------------------------------------------------------------ */
[data-ds-anim]{ will-change:opacity,transform }
[data-ds-anim="fade"],[data-ds-anim="rise"],[data-ds-anim="zoom"]{ opacity:0 }
[data-ds-anim="rise"]{ transform:translateY(40px) }
[data-ds-anim="zoom"]{ transform:scale(.92) }
/* The clip goes on what is inside, never on the observed element itself.
   An element with clip-path:inset(0 0 100%) reports an empty intersection
   rectangle, so IntersectionObserver never fires, so the class that would
   remove the clip is never added - and the picture stays invisible for good.
   Found by testing: a "reveal" image sat there clipped with nothing in the
   console to say why. */
[data-ds-anim="reveal"] > *{ clip-path:inset(0 0 100% 0);
  transition:clip-path 1.1s cubic-bezier(.22,1,.36,1) }
[data-ds-anim="reveal"].is-in > *{ clip-path:inset(0 0 0% 0) }
[data-ds-anim="parallax"]{ overflow:hidden }

[data-ds-anim].is-in{ opacity:1; transform:none;
  transition:opacity .8s ease,transform .8s cubic-bezier(.22,1,.36,1) }

/* GSAP drives the values itself once it is there, so the transition has to get
   out of the way or the two fight over the same properties. */
.ds-gsap [data-ds-anim].is-in{ transition:none }

@media (prefers-reduced-motion: reduce){
  [data-ds-anim]{ opacity:1 !important; transform:none !important }
  [data-ds-anim] > *{ clip-path:none !important }
  .ds-lb-open img{ transition:none }
}

/* ---------------------------------------------------------------------
 * The lightbox
 * ------------------------------------------------------------------ */
/* The page held still while the lightbox is over it.
 *
 * overflow:hidden takes the scrollbar away, and the page widens by exactly its
 * width the moment it goes - so a centered layout slides sideways on open and
 * back on close. Its width is given back as padding, so the content keeps the
 * room it had. --ds-sbw is published by the builder and is zero on an overlay
 * scrollbar, which is the right answer there. The lightbox itself is fixed to
 * the viewport and does not move with this. */
html.ds-lb-on{ overflow:hidden; padding-right:var(--ds-sbw,0px) }

.ds-lb{ position:fixed; inset:0; z-index:99999; display:grid; place-items:center;
  padding:max(16px, min(4vw, 56px), 2.9167vw) }
.ds-lb[hidden]{ display:none }
/* The dimming behind a Modal element, and behind the lightbox it shares its
   viewer with. The modal's own setting wins where one is chosen; the lightbox
   setting is what it falls back to, so a site that only ever set one still
   gets the look it asked for in both places. */
.ds-lb-back{ position:absolute; inset:0;
  background-color:var(--ds-lb-overlay,#080809);
  background-image:var(--ds-lb-image,none);
  background-size:cover; background-position:50% 50%;
  opacity:var(--ds-lb-opacity,.94);
  backdrop-filter:blur(var(--ds-lb-blur,14px));
  -webkit-backdrop-filter:blur(var(--ds-lb-blur,14px)) }

/* The modal's own dimming.
 *
 * Scoped to the modal rather than read on the shared rule above. Both are drawn
 * by the same viewer and the settings are written on the root, so a modal scrim
 * stated up there was changing the lightbox's overlay as well - one setting
 * quietly editing two things.
 *
 * Unset, this falls back to a figure of its own rather than to the lightbox's.
 * They are two elements: one is a picture viewer, the other is a panel of
 * content, they are set on two different screens, and a modal has no reason to
 * take its dimming from how somebody dressed a gallery. */
/* The opacity on the rule above is the gallery's, set on its own screen. A modal
   states its dimming as a color, alpha and all, so being faded a second time
   here both darkened a color somebody picked exactly and left the blur behind
   it with almost nothing to show through. */
.ds-lb-modal .ds-lb-back{
  background-color:var(--ds-modal-scrim, rgba(0,0,0,.72));
  opacity:1;
  backdrop-filter:blur(var(--ds-modal-blur, 14px));
  -webkit-backdrop-filter:blur(var(--ds-modal-blur, 14px)) }

.ds-lb-stage{ position:relative; max-width:min(1400px,100%); max-height:100%;
  display:grid; place-items:center }
/* Centered on both axes, not only across.
 *
 * justify-items was set and align-items was not, so the second kept its default
 * of stretch - and a grid item told to stretch with height:auto is pulled to
 * the height of its row rather than keeping its own. It only shows on a gallery
 * whose photographs are different shapes, which is why it survived: this
 * catalog has 296x640 pictures next to a 480x640, and the wide one came out
 * narrow. */
.ds-lb-figure{ margin:0; display:grid; gap:14px; place-items:center }
.ds-lb-figure img{ display:block; max-width:100%;
  /* Exactly the room the viewer leaves, rather than a figure that has to be
     kept in step with it by hand. .ds-lb pads itself by clamp(16px,4vw,56px)
     on every side, so the box a panel can occupy is the screen less twice
     that - and the old figure was a different guess entirely, which is how a
     modal asking for more height ended up hanging off the bottom of the
     screen with a gap above it. Written from the same clamp, so the two
     cannot drift. */
  max-height:calc(100svh - max(96px, min(16vw, 190px), 9.8958vw)); width:auto; height:auto;
  /* Its own shape, whatever a box around it decides.
     Both axes are auto here, so this changes nothing today - it is the
     guarantee that a picture is scaled rather than distorted if anything ever
     does constrain both, which is exactly how the stretch above happened. */
  object-fit:contain;
  align-self:center;
  border-radius:4px; box-shadow:0 40px 120px rgba(0,0,0,.6) }
.ds-lb-figure figcaption{ color:var(--ds-lb-caption,rgba(255,255,255,.62));
  font-size:var(--ds-lb-caption-size,max(calc(14px * var(--ds-lo, .5333)), 0.7292vw));
  letter-spacing:.01em; text-align:center; max-width:62ch }
.ds-lb-figure figcaption[hidden]{ display:none }
/* Captions turned off on the Lightbox screen. The same shape as ds-lb-nocount
   above it, because it is the same decision about a different part. */
.ds-lb-nocap .ds-lb-figure figcaption{ display:none }
/* And no close button, for a viewer closed by clicking away or by Escape. */
.ds-lb-noclose .ds-lb-x{ display:none }

/* Magnifying. The cursor is the only thing that says a picture can be looked at
   more closely, so it has to be right before the first click rather than after
   it. The stage stops clipping while magnified - the picture is meant to run
   past its own frame, that being the point of magnifying it. */
.ds-lb.can-zoom .ds-lb-figure img{ cursor:zoom-in; touch-action:none }
.ds-lb.is-zoomed .ds-lb-figure img{ cursor:grab; will-change:transform }
.ds-lb.is-zoomed .ds-lb-figure img:active{ cursor:grabbing }
.ds-lb.is-zoomed .ds-lb-stage{ max-width:100%; overflow:visible }

/* Snaps back rather than easing, because the drag has to keep up with the
   finger. The step in and out is the only part that is animated. */
.ds-lb-figure img{ transition:transform .18s cubic-bezier(.22,1,.36,1) }
.ds-lb.is-zoomed .ds-lb-figure img:active{ transition:none }

@media (prefers-reduced-motion: reduce){
  .ds-lb-figure img{ transition:none }
}

.ds-lb-x,.ds-lb-nav{ position:absolute; z-index:2; display:grid; place-items:center;
  width:var(--ds-lb-icon,52px); height:var(--ds-lb-icon,52px);
  /* Width and color both from Lightbox > Controls. The width was written in
     here, so the color could only ever be drawn as a hairline. */
  border:var(--ds-lb-icon-bw,1px) solid var(--ds-lb-icon-edge,rgba(255,255,255,.16));
  border-radius:var(--ds-lb-radius,50%);
  background:var(--ds-lb-icon-bg,rgba(255,255,255,.04));
  color:var(--ds-lb-ink,#fff); cursor:pointer; padding:0;
  transition:background var(--ds-lb-speed,.35s) ease,
    border-color var(--ds-lb-speed,.35s) ease,
    transform var(--ds-lb-speed,.35s) ease }
/* The hover colors from Lightbox > Controls. Each falls back to the hardcoded
   figure the control used before it could be set, so a site that has left them
   blank behaves exactly as it did. */
.ds-lb-x:hover,.ds-lb-nav:hover{
  background:var(--ds-lb-icon-bg-h, rgba(255,255,255,.12));
  border-color:var(--ds-lb-icon-edge-h, rgba(255,255,255,.4));
  color:var(--ds-lb-ink-h, var(--ds-lb-ink,#fff)) }
.ds-lb-x:focus-visible,.ds-lb-nav:focus-visible{ outline:2px solid #fff; outline-offset:3px }
.ds-lb-x svg,.ds-lb-nav svg{ width:20px; height:20px; fill:none; stroke:currentColor;
  stroke-width:1.6; stroke-linecap:round; stroke-linejoin:round }

.ds-lb-x{ top:max(14px, min(3vw, 28px), 1.4583vw); right:max(14px, min(3vw, 28px), 1.4583vw) }
.ds-lb-nav{ top:50%; margin-top:calc(var(--ds-lb-icon,52px) / -2) }
.ds-lb-nav.prev{ left:max(10px, min(2.5vw, 30px), 1.5625vw) }
.ds-lb-nav.next{ right:max(10px, min(2.5vw, 30px), 1.5625vw) }
.ds-lb-nav[hidden]{ display:none }

.ds-lb-count{ position:absolute; bottom:max(14px, min(3vw, 26px), 1.3542vw); left:50%;
  transform:translateX(-50%); color:rgba(255,255,255,.5); font-size:max(calc(13px * var(--ds-lo, .5333)), 0.6771vw);
  letter-spacing:.14em; font-variant-numeric:tabular-nums }
.ds-lb-nocount .ds-lb-count{ display:none }

/* ---- modals ----
   The lightbox viewer with a panel where the picture goes, so the animation and
   the focus trap are the ones already set. Everything the modal LOOKS like is
   its own: it is a separate element from the lightbox, set on its own screen,
   and unset it falls back to the theme rather than to the picture viewer. */
.ds-modal-panel{ position:relative; z-index:2; overflow-y:auto;
  /* Exactly the room the viewer leaves, rather than a figure that has to be
     kept in step with it by hand. .ds-lb pads itself by clamp(16px,4vw,56px)
     on every side, so the box a panel can occupy is the screen less twice
     that - and the old figure was a different guess entirely, which is how a
     modal asking for more height ended up hanging off the bottom of the
     screen with a gap above it. Written from the same clamp, so the two
     cannot drift. */
  max-height:calc(100svh - (max(16px, min(4vw, 56px), 2.9167vw) * 2));
  /* Unset, the padding stays fluid and the colors are the theme's. */
  padding:var(--ds-modal-pad, max(24px, min(4vw, 48px), 2.5vw));
  background:var(--ds-modal-bg, var(--ds-bg,#0b0b0c));
  color:var(--ds-modal-ink, var(--ds-text,#f5f5f5));
  border:var(--ds-modal-bw, 1px) solid
    var(--ds-modal-edge, var(--ds-line, rgba(255,255,255,.16)));
  border-radius:var(--ds-field-radius,4px) }

/* Headings inside the panel answer to their own color.
 *
 * They took the panel's text color, so a modal wanting a pale heading over
 * quieter body copy had one control for two jobs. Unset, they inherit exactly
 * as before. */
.ds-modal-panel :is(h1,h2,h3,h4,h5,h6),
.ds-modal-head{ color:var(--ds-modal-head, inherit) }

/* The stage takes the room, so a size on the panel means something.
 *
 * The lightbox centres its stage as a grid item, and a centred grid item is
 * shrink to fit - so the stage was as wide as whatever was inside it, and the
 * panel width of min(960px,100%) resolved its 100% against that. Measured on a
 * 1639px window: a panel asking for 960 came out 202. Small, medium and large
 * have never done anything at all; every modal in the suite has been drawn at
 * the width of its own text.
 *
 * Only while a modal is up. A picture in the lightbox is still sized by the
 * picture, which is what that grid item is for the rest of the time. */
.ds-lb-modal .ds-lb-stage{ width:min(1400px,100%) }

/* The picture figure is not in a modal.
 *
 * The viewer is shared: the same stage shows a photograph and a modal panel,
 * and the figure was left in the DOM when a modal opened. Empty it still drew
 * fourteen pixels - it is a grid with a gap - so it sat above the panel, pushed
 * it down by that much and took the same off the height the panel could use.
 * Measured: a panel that should have been 56 from the top and 56 from the
 * bottom came out 70 and 42. */
.ds-lb-modal .ds-lb-figure{ display:none }

.ds-modal-small { width:min(420px,100%) }
.ds-modal-medium{ width:min(680px,100%) }
.ds-modal-large { width:min(960px,100%) }
.ds-modal-full  { width:100%; max-height:100svh; border:0; border-radius:0 }

.ds-modal-panel > :first-child{ margin-top:0 }
.ds-modal-panel > :last-child{ margin-bottom:0 }
.ds-modal-head{ margin:0 0 14px }

/* Where the panel sits. The viewer is a grid, so this is one line each. */
.ds-lb-modal.ds-modal-at-top    { align-items:start }
.ds-lb-modal.ds-modal-at-bottom { align-items:end }
.ds-lb-modal.ds-modal-at-left   { justify-items:start }
.ds-lb-modal.ds-modal-at-right  { justify-items:end }

/* The modal's close button.
 *
 * The viewer draws one button and both elements appear in it, so this is where
 * the modal says what its own should look like. Dressing the lightbox on the
 * Lightbox tab used to dress this as well, which is one control over two things
 * that are set on two screens for two reasons.
 *
 * Every fallback here is the theme's, not the viewer's. */
/* The modal's own color, then the lightbox's, then white.
 *
 * These three rules are more specific than the .ds-lb-x rule above, so on a
 * modal they replace what Lightbox > Controls set rather than adding to it -
 * and each one read only its own --ds-modalx- variable, which is blank on a
 * site that has never opened Elements > Close button. So Icon set to white on
 * Lightbox > Controls was saved, written to the page as --ds-lb-ink, and then
 * thrown away here in favour of --ds-text: the page's own text color, black on
 * a light site, drawn on the dark scrim the close sits on.
 *
 * A chain instead. The modal's own color still wins wherever it is set, so a
 * modal can still be dressed differently from a gallery - which is why these
 * were separated in the first place. Left blank it now inherits the lightbox's
 * rather than silently ignoring it, and white is the last resort because the
 * button sits on a scrim and the one thing it has to do is read. */
.ds-lb-modal .ds-lb-x{
  color:var(--ds-modalx-ink, var(--ds-lb-ink, #fff));
  background:var(--ds-modalx-bg, var(--ds-lb-icon-bg, rgba(255,255,255,.04)));
  border-color:var(--ds-modalx-edge, var(--ds-lb-icon-edge, rgba(255,255,255,.16))) }
/* Given a color, hover keeps it and lifts it rather than jumping to the
   viewer's white - a chosen background that turns pale on hover reads as a
   fault rather than as a state. */
.ds-lb-modal .ds-lb-x:hover{
  /* The mark comes forward on the way to being clicked. White unless the
     Elements screen says otherwise - it sits on a scrim, and the one thing
     it has to do on hover is read. */
  color:var(--ds-modalx-ink-hover, var(--ds-lb-ink-h, #fff));
  background:var(--ds-modalx-bg, var(--ds-lb-icon-bg-h, rgba(255,255,255,.12)));
  border-color:var(--ds-modalx-edge, var(--ds-lb-icon-edge-h, rgba(255,255,255,.4)));
  filter:brightness(1.25) }

/* Which corner it sits in. All four stated, so each one clears the two edges
   the default rule set rather than only the ones it names. */
body.ds-modalx-at-tr .ds-lb-modal .ds-lb-x{
  inset:max(14px, min(3vw, 28px), 1.4583vw) max(14px, min(3vw, 28px), 1.4583vw) auto auto }
body.ds-modalx-at-tl .ds-lb-modal .ds-lb-x{
  inset:max(14px, min(3vw, 28px), 1.4583vw) auto auto max(14px, min(3vw, 28px), 1.4583vw) }
body.ds-modalx-at-br .ds-lb-modal .ds-lb-x{
  inset:auto max(14px, min(3vw, 28px), 1.4583vw) max(14px, min(3vw, 28px), 1.4583vw) auto }
body.ds-modalx-at-bl .ds-lb-modal .ds-lb-x{
  inset:auto auto max(14px, min(3vw, 28px), 1.4583vw) max(14px, min(3vw, 28px), 1.4583vw) }

/* The same four corners, of the panel.
 *
 * The button is moved inside the panel for these - a rule out here cannot hold
 * it against a box it is not in - and inset by the panel's own padding, so it
 * keeps the margin the content keeps and lands on the line the words start on
 * rather than in the corner of a box they never reach.
 *
 * --ds-modal-pad is only written when it has been set, so the fallback here is
 * the panel's own, stated the same way. */
body.ds-modalx-at-ptr .ds-lb-modal .ds-lb-x{
  inset:var(--ds-modal-pad, max(24px, min(4vw, 48px), 2.5vw))
        var(--ds-modal-pad, max(24px, min(4vw, 48px), 2.5vw)) auto auto }
body.ds-modalx-at-ptl .ds-lb-modal .ds-lb-x{
  inset:var(--ds-modal-pad, max(24px, min(4vw, 48px), 2.5vw)) auto auto
        var(--ds-modal-pad, max(24px, min(4vw, 48px), 2.5vw)) }
body.ds-modalx-at-pbr .ds-lb-modal .ds-lb-x{
  inset:auto var(--ds-modal-pad, max(24px, min(4vw, 48px), 2.5vw))
        var(--ds-modal-pad, max(24px, min(4vw, 48px), 2.5vw)) auto }
body.ds-modalx-at-pbl .ds-lb-modal .ds-lb-x{
  inset:auto auto var(--ds-modal-pad, max(24px, min(4vw, 48px), 2.5vw))
        var(--ds-modal-pad, max(24px, min(4vw, 48px), 2.5vw)) }

/* A word instead of a cross needs a pill, the same as the lightbox's does. */
body.ds-modalx-word .ds-lb-modal .ds-lb-x{
  width:auto; padding:0 16px; border-radius:999px }

/* A modal opened from a link rather than a button. */
.ds-modal-link{ padding:0; border:0; background:none; font:inherit;
  color:var(--ds-link); cursor:pointer; text-decoration:underline;
  text-underline-offset:.18em }
.ds-modal-link:hover{ color:var(--ds-link-hover) }

/* The close control as a word rather than a glyph. A circle sized to an icon
   cannot hold text, so it becomes a pill. */
.ds-lb-close-word .ds-lb-x{ width:auto; padding:0 16px; border-radius:999px }
/* The word itself is a setting now - it was hard coded to Close, so a site that
   wanted Back or Done had nowhere to say so. Drawn by CSS rather than written
   into the markup, so the button keeps the accessible name it was given. */
.ds-lb-x .ds-lb-word{ font-size:max(calc(13px * var(--ds-lo, .5333)), 0.6771vw); letter-spacing:.12em; text-transform:uppercase }
.ds-lb-x .ds-lb-word::after{ content:var(--ds-lb-close-word,'Close') }

/* On a phone the arrows sit under the picture, where a thumb can reach them. */
@media (max-width:640px){
  .ds-lb-nav{ top:auto; bottom:max(14px, min(4vw, 24px), 1.25vw); margin-top:0 }
  .ds-lb-nav.prev{ left:calc(50% - 74px) }
  .ds-lb-nav.next{ right:calc(50% - 74px) }
  .ds-lb-count{ bottom:calc(max(14px, min(4vw, 24px), 1.25vw) + 66px) }
}

/* Play and stop, and nothing else.
   One control over a piece of design, rather than the browser's full bar. */
.ds-video-simple{ position:relative }
.ds-video-btn{ position:absolute; inset:0; display:grid; place-items:center;
  width:100%; height:100%; padding:0; border:0; background:none; cursor:pointer;
  color:#fff }
.ds-video-btn i{ display:block; width:0; height:0; margin-left:6px;
  border-style:solid; border-width:14px 0 14px 22px;
  border-color:transparent transparent transparent currentColor;
  filter:drop-shadow(0 2px 12px rgba(0,0,0,.6));
  transition:opacity .2s ease }
/* Playing: the triangle becomes two bars, drawn from the same element so there
   is no second icon to keep in step. */
.ds-video-simple.is-playing .ds-video-btn i{ opacity:0 }
.ds-video-simple.is-playing .ds-video-btn:hover i{ opacity:1;
  width:18px; height:26px; margin:0; border:0;
  background:linear-gradient(90deg,currentColor 0 6px,transparent 6px 12px,currentColor 12px 18px) }

/* Scrubbed by the scroll: the film is the page, so it fills its box. */
.ds-video-scrub video{ width:100%; height:100%; object-fit:cover }

/* ---- blog posts ---- */
/* Cards follow Column gap on General - Layout - Rows, falling back to the
   page gutter while that is left at zero. The gutter is the space at the sides
   of the page, and using it here put fifty pixels between every post. */
.ds-posts{ display:grid; gap:var(--ds-card-gap, var(--ds-gutter,24px)) }
.ds-posts-grid,
.ds-posts-card{ grid-template-columns:repeat(var(--ds-post-cols,3),minmax(0,1fr)) }

/* Grid and masonry take the same room and the same corner as the rest.

   Every layout but these two had a rule of its own carrying the padding,
   so the Blog post cards card reached four of the six and looked broken on
   the other two. Nought and square unless asked for, which is what a bare
   grid has always been. */
/* A grid of posts is a wall of cards, so it is dressed like one - the same
   background, corner and room inside that Cards, Bento and Horizontal
   already take from Theme Settings, Elements, Blog post cards. It used to
   read the corner and the padding and not the background, so the only
   thing telling a card from the page behind it was the site wide shadow. */
.ds-posts-grid .ds-post-card,
.ds-posts-masonry .ds-post-card{
  /* background-color, never the shorthand: the shorthand resets
     background-image, and background-image is the layer a gradient border is
     painted on. A card with a gradient chosen drew nothing at all. */
  background-color:var(--ds-card-bg,#363636);
  border:var(--ds-card-bw,1px) solid var(--ds-card-line, transparent);
  padding:var(--ds-card-pt,14px) var(--ds-card-pr,14px)
          var(--ds-card-pb,14px) var(--ds-card-pl,14px);
  border-radius:var(--ds-card-radius,12px);
  /* A corner is only a corner if what is inside it is cut to match. */
  overflow:hidden }
.ds-posts-grid .ds-post-card-img,
.ds-posts-masonry .ds-post-card-img{
  margin:calc(var(--ds-card-pt,14px) * -1) calc(var(--ds-card-pr,14px) * -1) 14px }
.ds-posts-grid .ds-post-card-img img,
.ds-posts-masonry .ds-post-card-img img{
  border-radius:var(--ds-card-radius,12px) var(--ds-card-radius,12px) 0 0 }

/* Cards: the same grid, with each post standing on something. A grid of bare
   text on a dark page has nothing to say where one post ends and the next
   begins, which is the whole reason somebody asks for cards. */
.ds-posts-card .ds-post-card{
  display:flex; flex-direction:column;
  background-color:var(--ds-card-bg,#363636);
  border:var(--ds-card-bw,1px) solid var(--ds-card-line, color-mix(in srgb, var(--ds-text,#f5f5f5) 12%, transparent));
  border-radius:var(--ds-card-radius,12px); overflow:hidden;
  /* Four variables rather than one, because the picture below is pulled
     back out by exactly these and a shorthand cannot be negated. */
  padding:var(--ds-card-pt,14px) var(--ds-card-pr,14px) var(--ds-card-pb,14px) var(--ds-card-pl,14px);
  transition:border-color .25s ease, transform .25s ease }
.ds-posts-card .ds-post-card:hover{
  border-color:var(--ds-card-line-hover, currentColor);
  transform:translateY(var(--ds-card-lift,-3px)) }
.ds-posts-card .ds-post-card-img{ margin:calc(var(--ds-card-pt,14px) * -1) calc(var(--ds-card-pr,14px) * -1) 14px }
.ds-posts-card .ds-post-card-img img{ border-radius:var(--ds-card-radius,12px) var(--ds-card-radius,12px) 0 0 }

/* Bento: the same cards, unevenly. The first is given two columns and two
   rows, so the block opens on one picture rather than on a tidy row of
   identical ones - which is the only thing a bento is for. */
.ds-posts-bento{ grid-template-columns:repeat(var(--ds-post-cols,3),minmax(0,1fr));
  grid-auto-rows:minmax(160px,auto) }
.ds-posts-bento .ds-post-card{
  display:flex; flex-direction:column;
  background-color:var(--ds-card-bg,#363636);
  border:var(--ds-card-bw,1px) solid var(--ds-card-line, transparent);
  border-radius:var(--ds-card-radius,12px); overflow:hidden; padding:var(--ds-card-pt,14px) var(--ds-card-pr,14px) var(--ds-card-pb,14px) var(--ds-card-pl,14px) }
.ds-posts-bento .ds-post-card:first-child{ grid-column:span 2; grid-row:span 2 }
.ds-posts-bento .ds-post-card:nth-child(4){ grid-column:span 2 }

/* The picture takes whatever the words do not.
 *
 * A bento card is taller than its own content by design - that is what makes
 * the grid uneven - so anything that only sizes to its content leaves the rest
 * as bare card. Pushing the content to one end just moves the bare part to the
 * other. The picture grows into it instead, which is what the tall cell was
 * for. */
.ds-posts-bento .ds-post-card-img{
  order:-1; margin:calc(var(--ds-card-pt,14px) * -1) calc(var(--ds-card-pr,14px) * -1) 12px;
  flex:1 1 auto; min-height:120px; overflow:hidden }
.ds-posts-bento .ds-post-card-img img{
  width:100%; height:100%; object-fit:cover;
  border-radius:var(--ds-card-radius,12px) var(--ds-card-radius,12px) 0 0 }

/* Content height. The card stops where its words do rather than being
   stretched to the tallest in its row, and the picture goes back to its own
   shape instead of growing into slack that is no longer there.

   Written for the three layouts that lay cards out in a grid rather than for
   the bento alone: they all stretch, so a post with a two line headline used to
   set the height of every card beside it. */
.ds-posts-bento.is-cardh-fit,
.ds-posts-grid.is-cardh-fit,
.ds-posts-card.is-cardh-fit{ align-items:start }

.ds-posts-bento.is-cardh-fit .ds-post-card-img,
.ds-posts-grid.is-cardh-fit .ds-post-card-img,
.ds-posts-card.is-cardh-fit .ds-post-card-img{ flex:0 0 auto; min-height:0 }

.ds-posts-bento.is-cardh-fit .ds-post-card-img img,
.ds-posts-grid.is-cardh-fit .ds-post-card-img img,
.ds-posts-card.is-cardh-fit .ds-post-card-img img{ height:auto }

/* Horizontal: one row that scrolls sideways, snapping card to card. Every
   card keeps a readable width rather than sharing the row between however
   many posts were asked for. */
/* The frame the track slides inside. It is what gets pinned, so it clips - and
   nothing scrolls it by hand, which is why there is no scrollbar to look at.

   As tall as the window, because a pinned frame is held against the top of the
   screen and everything below it is the pin-spacer showing through. A frame
   400px tall left most of a screen empty, which reads as a gigantic gap; a
   frame the height of the window leaves none. The cards sit in the middle of
   it. */
.ds-posts-rail{ overflow:hidden;
  min-height:100svh; display:flex; align-items:center }

/* The track. As wide as the cards make it, moved by the page rather than by a
   scroll position, so no overflow and no snapping: both belonged to a scroll
   container this stopped being. */
/* Where a rail's first card lines up.

   The frame is the full width of the window, so left alone a rail begins
   hard against the edge of the screen. That suits a rail meant to read as
   though it carries on past the window, and reads as a mistake when the
   rest of the page is held inside a measure.

   Written against the track - anything hgallery slides - rather than
   against one plugin's wall, because the shop's products and the blog's
   posts are the same effect carrying different cards.

   Left of the page is the default, and is two parts: the page's own left
   edge, then the half scrollbar the bleed row has already shifted this
   frame by. A bleed row is 100vw wide and 100vw counts the scrollbar. */
.ds-fx-hgallery [data-track].is-railstart-page{
  padding-left:calc(
    max(
      var(--ds-gutter,24px),
      (100vw - var(--ds-sbw,0px) - var(--ds-max,1200px)) / 2 + var(--ds-gutter,24px)
    )
    + var(--ds-sbw,0px) / 2
  );
}

/* Edge of the screen: against the glass, not half a scrollbar behind it. */
.ds-fx-hgallery [data-track].is-railstart-edge{ padding-left:calc(var(--ds-sbw,0px) / 2) }

/* Already going. The first card is cut by the edge of the screen, which says
   the row began before the page did. A third of a card, because half reads
   as a card somebody forgot to finish. */
.ds-fx-hgallery [data-track].is-railstart-off{
  padding-left:0;
  margin-left:calc(var(--ds-sbw,0px) / 2 - var(--ds-rail-card, 320px) / 3);
}

.ds-posts-horizontal{ display:flex; width:max-content;
  --ds-rail-card:min(78vw,340px) }
.ds-posts-horizontal .ds-post-card{ flex:0 0 min(78vw,340px);
  display:flex; flex-direction:column;
  background-color:var(--ds-card-bg,#363636);
  border:var(--ds-card-bw,1px) solid var(--ds-card-line, transparent);
  border-radius:var(--ds-card-radius,12px); overflow:hidden; padding:var(--ds-card-pt,14px) var(--ds-card-pr,14px) var(--ds-card-pb,14px) var(--ds-card-pl,14px) }
.ds-posts-horizontal .ds-post-card-img{ margin:calc(var(--ds-card-pt,14px) * -1) calc(var(--ds-card-pr,14px) * -1) 12px }
.ds-posts-horizontal .ds-post-card-img img{ border-radius:var(--ds-card-radius,12px) var(--ds-card-radius,12px) 0 0 }

/* Masonry as CSS columns rather than a layout library - see the note in
   render_posts(). display:block because a grid container has no columns. */
.ds-posts-masonry{ display:block; column-count:var(--ds-post-cols,3);
  column-gap:var(--ds-card-gap, var(--ds-gutter,24px)) }

/* And fewer of them on a narrow screen.
 *
 * The count was the desktop answer at every width, so three columns of cards
 * stayed three columns on a phone - measured on a product page at 412px, the
 * screenshots came out 104px wide in three columns with thirty between them.
 * Card and bento carried the same gap. Two at tablet, one on a phone, across
 * all three layouts, so a column count chosen for a desktop no longer decides
 * what a phone is given. */


@media (max-width: 560px){
  .ds-posts-masonry{ column-count:1 }
  .ds-posts-card,
  .ds-posts-bento{ grid-template-columns:1fr }
}
.ds-posts-masonry .ds-post-card{ break-inside:avoid;
  margin-bottom:var(--ds-gutter,24px) }
/* Any child, not only a post card.
 *
 * break-inside was scoped to .ds-post-card, so the columns only held together
 * for the one element these rules shipped with. Anything else put in a masonry
 * container - a figure and its caption on a product page - split down the
 * middle and carried on in the next column. The same two declarations, said
 * once for whatever is in there. */
.ds-posts-masonry > *{ break-inside:avoid;
  margin-bottom:var(--ds-gutter,24px) }
/* A caption belongs to the picture above it.
 *
 * break-inside keeps one child whole; it says nothing about two. So a column
 * could end on a figure and start the next with its caption - which is what
 * happened to two of the nine cells on the Darkroom page. break-after moves
 * the pair together. */
.ds-posts-masonry > figure{ break-after:avoid }

/* And it sits directly under it.
 *
 * Every child took the gutter as its bottom margin, the picture included, so
 * a label stood the full row gap away from the thing it names and closer to
 * the next picture than to its own. The gap belongs after the pair, not inside
 * it: the picture holds its caption tight, and the caption carries the gutter. */
.ds-posts-masonry > figure{ margin-bottom:8px }

/* A masonry container is a container, not a card.
 *
 * The wrapper on a product page is a Box element, and .ds-box paints
 * --ds-box-bg - which the palette sets site wide - so the whole grid sat on a
 * grey panel nobody asked for. The pictures are the content here; there is no
 * card behind them. */
.ds-box.ds-posts-masonry{ background:transparent }

/* This said the same thing as the rule further up, twice, and said it with the
   older figures - so whichever set of defaults was written up there, these
   noughts came later and won. Grid and masonry are dressed once, at the top,
   with the rest of the card layouts. */

/* A plain list is plain. No background, no room inside it and no shadow -
   not even the site wide one, which is ticked for cards and reaches this
   through the same class. A list of posts is a list, and the moment it has
   a panel behind each line it is a wall of cards in a single column. */
.ds-posts-list .ds-post-card{ display:grid; gap:var(--ds-gutter,24px);
  grid-template-columns:minmax(0,1fr);
  background:none; padding:0; border-radius:0; box-shadow:none }


/* The frame the picture moves inside.

   overflow here rather than on the card: the list layout has no overflow of
   its own, so a picture told to zoom grew out over the words beside it. */
.ds-post-card-img{ overflow:hidden }

.ds-post-card-img img{ width:100%; height:auto; display:block;
  border-radius:var(--ds-field-radius,4px);
  /* No transition until one is asked for, so a card set to Nothing carries
     no animation at all rather than a zero length one. */
  transition:transform var(--ds-card-img-time,0s) ease }

.ds-post-card:hover .ds-post-card-img img{
  transform:translateY(var(--ds-card-img-y,0)) scale(var(--ds-card-img-scale,1)) }

/* Somebody who has asked their machine to stop moving things has asked for
   this too. */
@media (prefers-reduced-motion: reduce){
  .ds-post-card-img img,
  .ds-post-card:hover .ds-post-card-img img{ transition:none; transform:none }
}
/* The heading on a post card takes its type from H4, all of it.
 *
 * The size and the weight were read from the settings and the line height was
 * a flat 1.2 written here - so Theme Settings, Type, H4 moved two of the three
 * and the spacing between the lines of a two line post title could not be
 * changed from anywhere at all. The theme's own .ds-card-title has read the
 * token all along, which left two card headings on one site answering to
 * different things. */
/* The size comes from the level, and nothing else does.
 *
 * These four were pinned to H4 whatever tag the card was written as, so Title
 * level changed the tag and the type never moved - the setting looked broken
 * because the stylesheet was answering a question the setting had been given.
 *
 * font-size is deliberately absent, so the level decides it. The rest is put
 * back to the card's own: a card title wraps, and a heading's face, case and
 * leading are chosen for one line across a page. The same reason the shop's
 * card name needed it. */
.ds-post-card-title{
  margin:0;
  /* Everything comes from the level - font, size, case, tracking and leading.
     Choosing a heading level means the card title is that heading. */
  /* A card is a narrow column and these are display sizes - "Motorcycle" at
     70px is wider than the card it is in. Long words break rather than
     running out over the edge. */
  overflow-wrap:break-word;
}
/* The heading on a card, and the words under it.

   inherit rather than a color of their own where nothing is set, so a card
   left alone reads exactly as it did - the heading takes the page's heading
   color through the h3 and the text takes --ds-text. */
.ds-post-card-title,
.ds-post-card-title a{ color:var(--ds-card-head, inherit) }
.ds-post-card-title a{ text-decoration:none }

/* And on the way to being clicked. The whole card is the link - the anchor
   is stretched across it - so the hover is read off the card rather than
   off the heading, or only the words themselves would answer. */
.ds-post-card:hover .ds-post-card-title,
.ds-post-card:hover .ds-post-card-title a{
  color:var(--ds-card-head-hover, var(--ds-card-head, inherit)) }
.ds-post-card:hover .ds-post-card-text{
  color:var(--ds-card-text-hover, var(--ds-card-text, var(--ds-text))) }
.ds-post-card-meta{ margin:0 0 .4em; font-size:max(calc(13px * var(--ds-lo, .5333)), 0.6771vw); color:var(--ds-meta, var(--ds-accent));
  letter-spacing:.04em }
.ds-post-card-text{ margin:.6em 0 0; color:var(--ds-card-text, var(--ds-text)) }

/* A ratio box, so the page does not jump when the video loads. */
.ds-video{ position:relative; width:100%; overflow:hidden; border-radius:8px }
.ds-video video,.ds-video iframe{ position:absolute; inset:0; width:100%; height:100%; border:0 }
/* The film keeps its own shape inside the ratio it was given.
 *
 * Stretched to fill the box with no object-fit, the default is fill - so a 16:9
 * recording in a 4:3 frame is squashed vertically, and every ratio that is not
 * the film's own does the same. Cover crops instead, which is what the scrubbed
 * rule above already does and what a picture does site wide. An iframe ignores
 * this, which is correct: it has no intrinsic shape to preserve. */
.ds-video video{ object-fit:cover }
.ds-ratio-16-9{ aspect-ratio:16/9 }
.ds-ratio-4-3 { aspect-ratio:4/3 }
.ds-ratio-1-1 { aspect-ratio:1/1 }
.ds-ratio-9-16{ aspect-ratio:9/16 }

.ds-btn-wrap{ line-height:1 }

/* Two buttons together are a pair, not a stack.
 *
 * Each button is its own element and each element is a block, so a Get started
 * beside a Talk to us came out one under the other however they were aligned -
 * the alignment was obeyed and both were still on their own line. Neighbors
 * that agree about alignment go inline so they sit side by side and wrap only
 * when there is no room.
 *
 * Full width is left alone: a button asked to fill the column means it, and a
 * second one beside it is not what was asked for. */
.ds-btn-wrap:not(.ds-align-full):has(+ .ds-btn-wrap:not(.ds-align-full)),
.ds-btn-wrap:not(.ds-align-full) + .ds-btn-wrap:not(.ds-align-full){
  display:inline-flex;
  vertical-align:top;
}

.ds-btn-wrap + .ds-btn-wrap{ margin-left:var(--ds-btn-gap,12px) }

/* An inline pair takes its line from whatever holds it, so that has to be told
 * what the buttons already say. Left needs nothing - it is where a line starts.
 *
 * Every container an element can be dropped into, not just a column. A pair of
 * centered buttons inside a Box, a Modal or a tab panel sat hard left: the wrap
 * is inline-flex, so it shrinks to the button and an inline box is placed by
 * its parent's text-align, not by its own - and .ds-align-center on the wrap
 * only ever centered the label inside the button. The setting said center, the
 * button was centered, and the pair was in the corner. */
.ds-col:has(> .ds-btn-wrap.ds-align-center + .ds-btn-wrap.ds-align-center),
.ds-inrow-col:has(> .ds-btn-wrap.ds-align-center + .ds-btn-wrap.ds-align-center),
.ds-box:has(> .ds-btn-wrap.ds-align-center + .ds-btn-wrap.ds-align-center),
.ds-modal-body:has(> .ds-btn-wrap.ds-align-center + .ds-btn-wrap.ds-align-center),
.ds-tab-in:has(> .ds-btn-wrap.ds-align-center + .ds-btn-wrap.ds-align-center){ text-align:center }

.ds-col:has(> .ds-btn-wrap.ds-align-right + .ds-btn-wrap.ds-align-right),
.ds-inrow-col:has(> .ds-btn-wrap.ds-align-right + .ds-btn-wrap.ds-align-right),
.ds-box:has(> .ds-btn-wrap.ds-align-right + .ds-btn-wrap.ds-align-right),
.ds-modal-body:has(> .ds-btn-wrap.ds-align-right + .ds-btn-wrap.ds-align-right),
.ds-tab-in:has(> .ds-btn-wrap.ds-align-right + .ds-btn-wrap.ds-align-right){ text-align:right }

/* Filling the column. justify-content because .ds-btn is an inline-flex that
   centers its own label and icon - width alone would leave them at the left. */
.ds-btn-full .ds-btn{ display:flex; width:100%; justify-content:center; text-align:center }
/* A flex line, not an inline block.
 *
 * The theme sets inline-flex with align-items:center and this rule loads after
 * it, so every button on the site came out inline-block - and align-items does
 * nothing to one. The label and the arrow then sat on the text baseline, and
 * because the arrow's box is 20px against the label's 18, the words were
 * pushed down: measured 21px above the label and 15 below it in a 53px button.
 *
 * The gap is what puts the space between a label and its mark, and the theme's
 * icon rules are written expecting it - see the note beside .ds-btn-mask. */
.ds-btn{ display:inline-flex; align-items:center; justify-content:center;
  gap:var(--ds-btnicon-gap,.6em);
  padding:.85em 1.9em; border-radius:2px;
  font-size:max(calc(14px * var(--ds-lo, .5333)), 0.7292vw); text-decoration:none; border:1px solid var(--ds-btn-bg,#fff);
  background:var(--ds-btn-bg,#fff); color:var(--ds-btn-text,#000);
  /* Where the button sits is not how its words are set.
   *
   * The wrap is aligned with text-align, which is how an inline-block is
   * pushed to one side of a column - and text-align is inherited, so it went
   * straight on through the button and set the label too. Aligning a button
   * right therefore right-aligned its own words, whatever the button's own
   * text setting said. The button states its own, so nothing above it can
   * reach in. */
  text-align:center;
  transition:opacity .15s ease }

/* The two line button is dressed by the theme, which lays the lines out as
   full width blocks and sets each one with text-align. Nothing here, or there
   would be two answers to the same question. */
/* No opacity here.

   Fade is one of the hovers on the Buttons tab and it is written against
   body.ds-hover-fade, which is how a site picks it. This rule faded every
   button on every site whatever they had chosen - so a solid label went
   see-through on the way to being clicked, and the site's own hover was
   drawn on top of a fade it never asked for. */
.ds-btn:hover{ color:var(--ds-btn-text,#000) }
/* Scoped to buttons, because this is also a body class.
 *
 * The site's button preset is written onto <body> as ds-btn-<preset>, so a bare
 * .ds-btn-outline rule was not styling outline buttons - it was styling the
 * whole document. On a site whose preset is Outline every paragraph, heading
 * and menu item inherited the button's color, which is why the page text came
 * out red. The theme's own copy of this rule has always named .ds-btn; this one
 * never did. */
.ds-btn-outline .ds-btn:not(.ds-btn-solid):not(.ds-btn-secondary),
.ds-btn.ds-btn-outline{ background:transparent; color:var(--ds-btn-bg,#fff) }
.ds-btn-outline .ds-btn:not(.ds-btn-solid):not(.ds-btn-secondary):hover,
.ds-btn.ds-btn-outline:hover{ background:var(--ds-btn-bg,#fff); color:var(--ds-btn-text,#000); opacity:1 }

/* The height is written on the element now, as the figure the designer typed.
   The four named sizes are gone: a gap is a distance, not a t-shirt size. */

/* Marquee.
   The track holds four copies and travels exactly one copy's width, so the
   loop has no seam. Moving a single copy off-screen and restarting it is the
   usual approach and it visibly stutters. */
.ds-marquee{ overflow:hidden; width:100%; -webkit-mask-image:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent);
  /* The band. Every one of these is left to the theme until the element is
     given a figure of its own - see the marquee colors on Elements. */
  background:var(--ds-mq-bg, transparent);
  color:var(--ds-mq-ink, inherit);
  padding-top:var(--ds-mq-pt, 0);
  padding-bottom:var(--ds-mq-pb, 0);
  /* The theme's marquee shadow until the element chooses one of its own -
     which is what "Theme" means on the element's Shadow list. */
  box-shadow:var(--ds-shadow, none) }

/* Tilted, with the ends still off the screen.
 *
 * Rotating a strip pulls its corners inward: at ten degrees a full width band
 * shows both of its ends, and a marquee whose ends are visible has stopped
 * being endless. The width is divided by the cosine of the angle, which is
 * exactly the amount that puts them back outside, plus a screen height's worth
 * of tangent for the extra reach the band's own height adds as it leans.
 *
 * Centered with a translate rather than a margin so the widening stays
 * symmetrical whatever the angle is.
 *
 * overflow-x:clip on the wrapper rather than hidden: clip does not create a
 * scroll container, so the vertical overhang of the tilt is still drawn while
 * the horizontal ends are cut. hidden would have clipped both and sliced the
 * top and bottom off the letters. */
.ds-marquee-tilt{
  overflow:visible;
  transform:translateX(-50%) rotate(var(--ds-mq-rot, 0deg));
  margin-left:50%;
  width:calc(100% / cos(var(--ds-mq-rot, 0deg)) + 30vh * tan(abs(var(--ds-mq-rot, 0deg)))) }

/* The clip belongs to the page, not to the band - a band that clipped itself
   would cut off its own tilt. */
body:has(.ds-marquee-tilt){ overflow-x:clip }

@supports not (width: calc(1px / cos(10deg))){
  /* No trigonometry: widen by a flat third, which covers every angle the
     control allows without needing to know which one was chosen. */
  .ds-marquee-tilt{ width:134% }
}

/* Under the band. Presets rather than six numbers, because a shadow on a strip
   is a depth and not a geometry. */
.ds-marquee-sh-none  { box-shadow:none }
.ds-marquee-sh-soft  { box-shadow:0 6px 24px rgba(0,0,0,.28) }
.ds-marquee-sh-medium{ box-shadow:0 10px 34px rgba(0,0,0,.42) }
.ds-marquee-sh-strong{ box-shadow:0 16px 48px rgba(0,0,0,.6) }
.ds-marquee-track{ display:flex; width:max-content; will-change:transform;
  /* Words and logos both sit on the middle of the band. A line of type and a
     row of pictures are rarely the same height, and left to the top of the row
     the shorter of the two hangs off the top edge. */
  align-items:center;
  animation:ds-marquee 9s linear infinite }

/* The repeats are wrapped so they can be hidden from a screen reader in one
   go. display:contents takes the wrapper out of the layout again, so the track
   is the same row of items it was before the wrapper existed. */
.ds-marquee-track > [aria-hidden="true"]{ display:contents }
/* The spacing lives on the separator, not on the text.
   With padding on the item, every join carried two paddings while the joins
   written inside the text carried one space, so the point where the loop
   restarted was visibly wider than every other join - the gap the strip
   appeared to have after its last word. */
/* Leading of its own.
 *
 * The size here is a heading size and the line height was whatever the page
 * around it had - a body line box of twenty four pixels holding forty four
 * pixel letters, with the strip clipping everything that did not fit. So the
 * tops of the words were cut off. Proportional, so it holds whatever size the
 * heading scale is set to. */
.ds-marquee-item,.ds-marquee-sep{ display:block; white-space:nowrap;
  /* Its own face, size and case, all from the Marquee card on Elements.
     Each falls through to what a running line looked like before it had a
     setting: the heading font, the H2 size, and the words as typed. */
  font-family:var(--ds-mq-font, var(--ds-font-heading,inherit));
  font-size:var(--ds-mq-size, var(--ds-h2-size,44px));
  text-transform:var(--ds-mq-case, none);
  /* The line box is the type and nothing else.
   *
   * At 1.2 the extra fifth of a line is leading, and a face whose capitals sit
   * high in the em does not get it back evenly - so a band of capitals came out
   * with a strip of background under the letters and almost none above, reading
   * as text that had slipped down inside its own band. One flat line makes the
   * box the type's own height and the track centers it.
   *
   * Settable from Theme Settings, Elements, Marquee, and 1 where nothing is set -
   * so every band drawn before the field existed is untouched by it. */
  line-height:var(--ds-mq-lh, 1);
  /* And the last of it. Even at 1 an em box carries room above the capitals for
     accents and below the baseline for descenders, and a line of capitals uses
     neither. Trimming to the cap height and the baseline fits the band to the
     letters rather than to the font's metrics. Ignored where it is not
     understood, which leaves the line at 1 and still even. */
  /* Stood down the moment a line height is set.
   *
   * Trimming to the cap height and the baseline throws away exactly the space a
   * line height adds, so the two cannot both be in force: the figure was written
   * to the element, computed correctly, and the band did not move a pixel -
   * 168px of line in an 87px box. The theme writes --ds-mq-trim:normal alongside
   * the height, so asking for one turns off the other and nothing else changes
   * for a marquee that never set one. */
  text-box:var(--ds-mq-trim, trim-both cap alphabetic);
  font-weight:var(--ds-h2-weight,600);
  /* The marquee's own ink first.
     The band sets `color` on the strip, but this rule sets it again on the
     words - and a color declared on an element beats one it would have
     inherited, so the Text swatch on Elements and the element's own color
     both did nothing. Read here as well, ahead of the heading color these
     fall back to. */
  color:var(--ds-mq-ink, var(--ds-heading,inherit)) }
/* Air on both sides of the separator, from the field beside it. Left unset it
   is the .35em it has always been.

   And its own color, which the mark inside it inherits. After the rule that
   colors the words rather than fighting it: both are (0,1,0), so this one
   wins by coming later. Blank falls all the way back to the text color, which
   is what the separator took before it could be colored on its own - so a
   band with a black line and a white mark between the words is now something
   that can be asked for. */
.ds-marquee-sep{ padding-inline:var(--ds-mq-sepgap, .35em);
  color:var(--ds-mq-sepink, var(--ds-mq-ink, var(--ds-heading, inherit)));
  /* And the mark takes that color too.
   *
   * .ds-icon reads --ds-icon-ink, which Theme Settings sets once for the whole
   * site - an inherited custom property, so it reaches in here past every
   * color this band has set and paints the mark with it. A white icon ink and
   * a black separator gave a white wheel on a band that had been told twice to
   * be black, with nothing on the element able to change it. Pointing the
   * token at currentColor hands the mark back to the separator's own color,
   * which is the control that exists for exactly this. */
  --ds-icon-ink:currentColor;
  /* Centered on the line rather than sitting on it.
   *
   * The separator is a block sharing a row with the words, and what is inside
   * it was inline - so an icon sat with its foot on the text baseline, which is
   * nowhere near the middle of a line of capitals. A character was no better,
   * because an asterisk is drawn high in its own em. Centering here puts the
   * mark on the middle of the band whatever it is. */
  display:flex; align-items:center; justify-content:center }

/* A mark instead of a character.
 *
 * Sized in em so it follows the marquee's own type size rather than needing a
 * figure of its own, and it takes the line's color like the words do. */
/* Sized in em so it follows the marquee's own type size, and left to take its
   color the way the words do.
   No fill here: DS_Icons writes fill and stroke as attributes, and an outline
   set says fill="none". A fill in the stylesheet beats a presentation
   attribute, so declaring one filled every outline icon in solid and turned a
   drawn mark into a blob. */
.ds-marquee-sepicon{ width:1em; height:1em; display:block }

/* The separator turning on the spot.
 *
 * The turn is on the inner span, not on .ds-marquee-sep itself: that one
 * carries the padding holding the gap open, and rotating a padded box swings
 * its padding round with it, which shoves the words either side. This turns
 * only what sits in the middle. A typed character is wrapped in the same span
 * by the renderer, so a character and a mark turn identically. */
.ds-marquee-sepin{ display:inline-block; line-height:1 }

.ds-marquee-sepspin .ds-marquee-sepin{
  animation:ds-mq-septurn var(--ds-mq-sepspin, 6s) linear infinite }

@keyframes ds-mq-septurn{ to{ transform:rotate(var(--ds-mq-septurn, 360deg)) } }

/* Turned by the scroll rather than by a clock.
 *
 * One full turn as the band crosses the window, so the mark answers the page
 * instead of running to its own time - and it stops dead when the reader does,
 * which a looping animation never does.
 *
 * Inside @supports because the declaration that makes this scroll driven is
 * animation-timeline: without it the browser keeps the animation and drops
 * only the timeline, which would spin the mark once on load and then stop -
 * the one behavior nobody asked for. No timeline, no animation. */
@supports (animation-timeline: view()){
  .ds-marquee-sepscroll .ds-marquee-sepin{
    animation:ds-mq-septurn 1s linear;
    animation-timeline:view();
  }
}

@media (prefers-reduced-motion: reduce){
  .ds-marquee-sepspin .ds-marquee-sepin,
  .ds-marquee-sepscroll .ds-marquee-sepin{ animation:none }
}
/* An empty separator still has to hold the gap open, or the words touch - but
   only until somebody says otherwise. This is (0,2,0) against the (0,1,0) of
   the rule above, so a flat .45em here beat the setting every time: a marquee
   with no separator and its gap set to zero still showed the same gap, most
   visibly at the seam where the last repeat meets the first. */
.ds-marquee-sep:empty{ padding-inline:var(--ds-mq-sepgap, .45em) }

/* The same pace as the directional marquee, which is the other running line in
   this theme and the one these were being judged against.
 *
 * That one covers half its track in 22s at medium - eleven seconds a quarter -
 * while this covered a quarter in 28, so the same word at the same setting
 * crawled here and moved there. The figures below are its durations doubled,
 * because this keyframe travels a quarter where that tween travels a half. */
/* The figures below are a fallback and nothing more.
 *
 * A duration is not a speed: what the line covers in it is one copy of itself,
 * and that is as long as whatever was typed in. A band of forty three items
 * measures ten and a half thousand pixels a copy, so sixteen seconds of "slow"
 * was 657 pixels a second and five of "fast" was two thousand - all three past
 * reading, and close enough alike that changing the setting looked like it did
 * nothing at all. The script works the duration back from the width so each
 * speed is a fixed number of pixels a second, and writes it here. These stand
 * only until it has measured, or where it never runs. */
.ds-marquee-slow   .ds-marquee-track{ animation-duration:var(--ds-mq-secs,16s) }
.ds-marquee-medium .ds-marquee-track{ animation-duration:var(--ds-mq-secs,9s) }
.ds-marquee-fast   .ds-marquee-track{ animation-duration:var(--ds-mq-secs,5s) }
.ds-marquee-right  .ds-marquee-track{ animation-direction:reverse }

/* Hard edges: the line is cut off by the box rather than fading into it. The
   fade is a mask, so turning it off is removing the mask - there is nothing to
   paint over. */
.ds-marquee-hard{ -webkit-mask-image:none; mask-image:none }

/* Driven by the scroll: nothing runs on its own, so the animation goes. Left
   on, the track would be positioned by one thing and moved by another. */
.ds-marquee-scrolled .ds-marquee-track{ animation:none }

@keyframes ds-marquee{ from{ transform:translateX(0) } to{ transform:translateX(-25%) } }

/* A strip that never stops moving is a real accessibility problem, not a
   preference - so it stops, rather than merely slowing down. */
@media (prefers-reduced-motion:reduce){
  .ds-marquee-track{ animation:none; transform:none }
  .ds-marquee{ overflow-x:auto }
}

/* ---- row clipping ---- */

/* The row is cut, not covered. A wedge drawn behind it only works over a flat
   background - clipping the row itself means the shape applies to whatever it
   holds: a photograph, a running shader, text. --ds-clip is how deep the cut
   goes, set per row. */
.ds-row-clip{ --ds-clip:60px }

/* Down: the bottom edge falls left to right. Up: it rises. */
.ds-clip-diag-down{ clip-path:polygon(0 0, 100% 0, 100% calc(100% - var(--ds-clip)), 0 100%) }
.ds-clip-diag-up{ clip-path:polygon(0 0, 100% 0, 100% 100%, 0 calc(100% - var(--ds-clip))) }

/* Both edges lean the same way, so a run of rows reads as one continuous
   diagonal rather than a stack of wedges. */
.ds-clip-diag-both{ clip-path:polygon(0 var(--ds-clip), 100% 0, 100% calc(100% - var(--ds-clip)), 0 100%) }

/* A point in the middle of the bottom edge, or the top. */
.ds-clip-point-down{ clip-path:polygon(0 0, 100% 0, 100% calc(100% - var(--ds-clip)), 50% 100%, 0 calc(100% - var(--ds-clip))) }
.ds-clip-point-up{ clip-path:polygon(0 var(--ds-clip), 50% 0, 100% var(--ds-clip), 100% 100%, 0 100%) }

/* A notch taken out rather than a point added. */
.ds-clip-notch-down{ clip-path:polygon(0 0, 100% 0, 100% 100%, 50% calc(100% - var(--ds-clip)), 0 100%) }
.ds-clip-notch-up{ clip-path:polygon(0 0, 50% var(--ds-clip), 100% 0, 100% 100%, 0 100%) }

/* Both edges slanted the same direction, for a row that leans. */
.ds-clip-slant-left{ clip-path:polygon(0 var(--ds-clip), 100% 0, 100% calc(100% - var(--ds-clip)), 0 100%) }
.ds-clip-slant-right{ clip-path:polygon(0 0, 100% var(--ds-clip), 100% 100%, 0 calc(100% - var(--ds-clip))) }

/* A clipped row must not leave a gap under it - the cut removes height the
   next row should occupy, so it is pulled up by the depth of the cut. */
.ds-clip-diag-down + .ds-row,
.ds-clip-diag-up + .ds-row,
.ds-clip-point-down + .ds-row,
.ds-clip-notch-down + .ds-row{ margin-top:calc(var(--ds-clip,60px) * -1) }

/* Nothing is laid out into the part being cut off.
 *
 * A clip removes pixels from an edge and nothing was keeping the content out of
 * them, so whatever landed in that band was simply cut in half. It went unnoticed
 * while everything sat at the top of its row, and appeared the moment content was
 * aligned to the bottom - which puts it precisely where the cut is.
 *
 * The row keeps its own padding and gains the depth of the cut on the edges that
 * are actually cut, so the shape eats space rather than content. Written per
 * shape because each one cuts a different edge: reserving both edges everywhere
 * would leave a band of nothing above every row whose shape only cuts below. */
/* On the inner box, which has no padding of its own, so this adds to whatever
   the row is padded by rather than replacing it - the row's own padding is
   written inline on the row itself and would win over anything here. */
.ds-row-clip.ds-clip-diag-down > .ds-row-in,
.ds-row-clip.ds-clip-diag-up > .ds-row-in,
.ds-row-clip.ds-clip-point-down > .ds-row-in,
.ds-row-clip.ds-clip-notch-down > .ds-row-in{ padding-bottom:var(--ds-clip,60px) }

.ds-row-clip.ds-clip-point-up > .ds-row-in,
.ds-row-clip.ds-clip-notch-up > .ds-row-in{ padding-top:var(--ds-clip,60px) }

/* These three lean, so both edges lose a wedge. */
.ds-row-clip.ds-clip-diag-both > .ds-row-in,
.ds-row-clip.ds-clip-slant-left > .ds-row-in,
.ds-row-clip.ds-clip-slant-right > .ds-row-in{
  padding-top:var(--ds-clip,60px);
  padding-bottom:var(--ds-clip,60px);
}

/* ---- row shape ----

   The same edges as the clip above, cut into what is behind the row instead of
   into the row. Clipping the row takes the content with it and removes height
   the next row then has to be pulled up into, which is why a diagonal there
   both sliced the words and dragged the following row over this one. A
   background layer can be cut without either happening: the row keeps its
   height, the content keeps its place, and the shape is only a shape.

   Every layer a row can be wearing is cut by the one rule - the color or
   gradient layer, the picture, the video, and the shader's canvas - so a shape
   looks the same whatever the background happens to be. */
.ds-rowshape{ --ds-shape:60px }

.ds-rowshape > .ds-row-bg,
.ds-rowshape > .ds-row-gl{ clip-path:var(--ds-shape-path, none) }

.ds-rowshape-diag-down { --ds-shape-path:polygon(0 0, 100% 0, 100% calc(100% - var(--ds-shape)), 0 100%) }
.ds-rowshape-diag-up   { --ds-shape-path:polygon(0 0, 100% 0, 100% 100%, 0 calc(100% - var(--ds-shape))) }
.ds-rowshape-diag-both { --ds-shape-path:polygon(0 var(--ds-shape), 100% 0, 100% calc(100% - var(--ds-shape)), 0 100%) }
.ds-rowshape-point-down{ --ds-shape-path:polygon(0 0, 100% 0, 100% calc(100% - var(--ds-shape)), 50% 100%, 0 calc(100% - var(--ds-shape))) }
.ds-rowshape-point-up  { --ds-shape-path:polygon(0 var(--ds-shape), 50% 0, 100% var(--ds-shape), 100% 100%, 0 100%) }
.ds-rowshape-notch-down{ --ds-shape-path:polygon(0 0, 100% 0, 100% 100%, 50% calc(100% - var(--ds-shape)), 0 100%) }
.ds-rowshape-notch-up  { --ds-shape-path:polygon(0 0, 50% var(--ds-shape), 100% 0, 100% 100%, 0 100%) }
.ds-rowshape-slant-left{ --ds-shape-path:polygon(0 var(--ds-shape), 100% 0, 100% calc(100% - var(--ds-shape)), 0 100%) }
.ds-rowshape-slant-right{ --ds-shape-path:polygon(0 0, 100% var(--ds-shape), 100% 100%, 0 calc(100% - var(--ds-shape))) }

/* ---- background text ----
   A word set large behind the content. It sits after the picture and the
   shader canvas in the markup so it paints over them, and before .ds-row-in so
   everything in the columns stays on top of it. Deliberately unstyled beyond
   position and size - the class is shown in the builder so it can be styled in
   CSS rather than through a settings screen nobody wants to fill in. */
/* The row has to be the thing it is positioned against.
   A row is only position:relative when it carries background media or a
   shader - a plain row is static, so an absolutely positioned child skipped it
   and measured against the page instead. That put the text hundreds of pixels
   outside its own row and at the page's width rather than the row's, which is
   both of the ways this looked broken. */
.ds-row:has(> .ds-rowbgtext){ position:relative; isolation:isolate }

/* Over the media and its dim, under the content.

   z-index 1 put these level with .ds-row-dimmed::after, which is the last
   child of the row - so with nothing between them the wash painted second
   and took the lettering down with the picture. Two is above the wash and
   still below .ds-row-in. */
.ds-rowbgtext{
  position:absolute; inset:0; z-index:2;
  /* Set from the row's own Dim on the background text, and only written
     when it is asked for. */
  opacity:var(--ds-rowbgtext-dim,1);
  display:flex; pointer-events:none;
  overflow:hidden;
  /* Held to the same measure as the row's own content.

     inset:0 covers the whole row element, which for a normal width row is the
     full viewport - so the text ignored the content width entirely and ran
     edge to edge whatever the row was set to. This reproduces where
     .ds-row-in actually puts its first column.

     Which is the page margin AND the gutter. .ds-row-in is capped at --ds-max
     and then pads a gutter inside that cap, so its content starts one gutter
     further in than the cap does. The old formula stopped at the cap, so on
     any window wider than the content width the words hung a full gutter past
     the columns at both ends - and under the cap it read as the text simply
     being wider than the page.

     max with 0 rather than with the gutter, because the gutter is added
     afterwards either way - a narrow window is nought margin and one gutter,
     which is exactly what the row does. */
  padding-inline:calc(max(0px, (100% - var(--ds-max,1200px)) / 2) + var(--ds-gutter,24px));
  /* The text color the theme settings set, and nothing else.
     It used to be mixed down to a fraction of that color on the reasoning that
     a word this large wants to sit back - which is a design decision, and not
     one this stylesheet gets to make on a site whose colors are set in the
     panel. The row option below writes --ds-rowbgtext when somebody wants a
     different color; with no override it is simply the text color. */
  color:var(--ds-rowbgtext, currentColor);
  /* The full width font from Typography, which is the setting made for this.
   *
   * The theme writes --ds-font-fullwidth from that setting and falls back to
   * the heading font when it is left blank, so naming the heading font here
   * meant the setting existed, was filled in, and changed nothing: a word set
   * in a condensed display face and blown up to fill a row reads as a pattern
   * of vertical strokes rather than as a word. */
  font-family:var(--ds-fullwidth-family, var(--ds-font-fullwidth, var(--ds-font-heading)));
  /* The rest of that same Typography row, not only its font.
   *
   * The family was wired up and nothing else was, so Full width text could be
   * given a weight, a case and a tracking, and a word used as a graphic - the
   * one place those settings matter most - ignored all three and stayed at the
   * 700 and the .9 written here. The old figures are the fallbacks, so a site
   * that has never opened that row looks exactly as it did. */
  font-weight:var(--ds-fullwidth-weight, 700);
  /* Room for the parts of the letters that fall outside the line box.

     A line height of .9 is shorter than the glyphs it holds - which is the
     point of it for a word used as a graphic - so the tail of a p and the
     overshoot on an o hang past the box. This one clips, and the word is
     pinned to one edge of it, so whichever edge that is takes the letters
     off: at the bottom "products" lost the bottom of every round letter and
     the whole tail of the p.

     In em of the word, not of this box, so it scales with the type - which
     is why it is on the span rather than here. */
  /* The row's own answer first, then the Typography row, then nothing.
     --ds-rowbgtext-case is written by no part of the suite today, so on its own
     this line could only ever say "none" however the setting was left. */
  text-transform:var(--ds-rowbgtext-case, var(--ds-fullwidth-case, none));
  user-select:none;
}

/* Heading sized, the same fluid scale a hero uses. */
/* Line height and tracking belong on the span, not on the box around it.
 *
 * Typography writes both in em, and an em on the box is worked out against the
 * box's own 17px and inherited as that many pixels - so a word set at 315px
 * came out with a 17.85px line box and a third of a pixel of tracking. The span
 * is the thing that carries the size, so the em means what it says there.
 *
 * The old figures stay as the fallbacks: .9 is deliberately tighter than the
 * glyphs, which is what makes a word read as a graphic. */
.ds-rowbgtext span{
  padding-block:.16em;
  line-height:var(--ds-fullwidth-lh, .9);
  letter-spacing:var(--ds-fullwidth-track, 0);
}

.ds-rowbgtext-heading span{ font-size:var(--ds-rowbgtext-size, max(3rem, min(12vw, 10rem), 8.3333vw)) }

/* Full width: the word stretches to fill the row whatever its length, which is
   what makes a single word read as a graphic rather than as a heading. */
.ds-rowbgtext-full span{
  display:block; width:100%;
  font-size:var(--ds-rowbgtext-size, 18vw);
  text-align:justify;
  white-space:nowrap;
}

/* A full width row keeps only its gutter, and a bleed row not even that -
   matching what .ds-row-in is given at each width. */
.ds-row-full > .ds-rowbgtext { padding-inline:var(--ds-gutter,24px) }

/* Inside a shell the row pads nothing, because the shell already has. The
   words follow it, or they sit a gutter in from columns that are not. */
.ds-shell .ds-row-normal > .ds-rowbgtext{
  padding-inline:max(0px, calc((100% - var(--ds-max,1200px)) / 2)) }
.ds-row-bleed > .ds-rowbgtext{ padding-inline:0 }

.ds-rowbgtext-hleft  { justify-content:flex-start }
.ds-rowbgtext-hcenter{ justify-content:center }
.ds-rowbgtext-hright { justify-content:flex-end }
.ds-rowbgtext-vtop   { align-items:flex-start }
.ds-rowbgtext-vmiddle{ align-items:center }
.ds-rowbgtext-vbottom{ align-items:flex-end }

/* Held against the window rather than the row, so the row scrolls past it.
 *
 * This was position:fixed, which is held against the window and nothing else:
 * the words stayed on screen for the whole page, riding over every section
 * after the row - and over the footer, which is where anybody would notice it.
 * A fixed element cannot be clipped by an ancestor that scrolls, so there was
 * no way to keep it inside its own row.
 *
 * It is a normal layer in the row now, and the words are pushed back by exactly
 * as much as the row has scrolled. They look held against the window, and they
 * are cut off with the row because that is what they are inside. */
.ds-rowbgtext-fixed span,
.ds-rowbgtext-parallax span{
  will-change:transform;
  transform:translate3d(0, var(--ds-bgtext-y, 0), 0);
}

/* Both offsets are written by bgtext.js, so with no JavaScript the words sit
   still rather than disappearing. */
@media (prefers-reduced-motion:reduce){
  .ds-rowbgtext-fixed span,
  .ds-rowbgtext-parallax span{ transform:none }
}

/* The glowing cut edge.
   A bare layer filling the row, behind everything, carrying only the glow. It
   is clipped with the row, so its shadow traces the cut - and because there is
   nothing inside it, no heading or picture gets outlined the way it did when
   the filter sat on the row itself. */
.ds-enter-glow{
  position:absolute; inset:0; z-index:0;
  pointer-events:none; display:block;
}
/* Above it, so the glow never washes over the content it is behind. */
.ds-enter > .ds-row-in{ position:relative; z-index:1 }

/* ---- quotations ----
   A blockquote with either a rule down one side or the marks themselves. The
   marks are drawn here rather than typed into the text: written into the words
   they cannot be turned off, and a screen reader reads them out.

   Sizes come from the type scale the rest of the site is set in - a pull quote
   picked out of thin air is the one thing on the page that belongs to nothing. */
/* A blockquote carries 40px of margin on both sides from the browser's own
   stylesheet, which nothing here was undoing - so a quotation sat inset from
   the column while every other element started at its edge. The element's own
   margin fields still apply when they are set; this is only the browser's. */
.ds-quote{ margin-top:0; margin-inline:0; color:var(--ds-heading) }
.ds-quote .ds-quote-text > :first-child{ margin-top:0 }
.ds-quote .ds-quote-text > :last-child{ margin-bottom:0 }

.ds-qsize-h1 .ds-quote-text{ font-size:var(--ds-h1-size); line-height:var(--ds-h1-lh,1em) }
.ds-qsize-h2 .ds-quote-text{ font-size:var(--ds-h2-size); line-height:var(--ds-h2-lh,1em) }
.ds-qsize-h3 .ds-quote-text{ font-size:var(--ds-h3-size); line-height:var(--ds-h3-lh,1em) }
.ds-qsize-h4 .ds-quote-text{ font-size:var(--ds-h4-size); line-height:var(--ds-h4-lh,1em) }
.ds-qsize-p  .ds-quote-text{ font-size:var(--ds-p-size);  line-height:var(--ds-p-lh,1.4em) }
/* The words take the quotation's own color where one was chosen, and otherwise
   whatever the row or the theme already gives them. Only the name could be
   colored before, so the two halves of one quotation could not be set to the
   same color as each other. */
.ds-quote .ds-quote-text{ font-family:var(--ds-font-heading); color:var(--ds-quote-ink, inherit) }
.ds-qsize-p .ds-quote-text{ font-family:var(--ds-font-text) }

/* Who said it. Always at reading size - a name set as large as the quotation
   competes with it. */
/* Nothing hangs below the last line. The rule down the side is exactly as tall
   as the quotation and the name it belongs to. */
.ds-quote > :last-child{ margin-bottom:0 }
/* One color for the whole line, and the quotation may set it.
 *
 * The name was the heading color and the title the accent, which is two
 * colors for one line about one person - a distinction nobody is making, and
 * the reason they never looked like they belonged together. --ds-quote-by is
 * written on the blockquote where a color has been chosen for it. */
.ds-quote-by{ display:block; margin:16px 0 0; font-style:normal;
  font-family:var(--ds-font-text); font-size:var(--ds-p-size);
  line-height:var(--ds-p-lh,1.4em); color:var(--ds-quote-by, var(--ds-accent)) }
.ds-quote-by cite{ font-style:normal }
.ds-quote-who{ color:inherit }
/* A dash between the name and the role, in the role's own color.
 *
 * On the role rather than after the name, so it takes the color the role is
 * set in - a separator that matches the name it follows reads as part of it. */
.ds-quote-role::before{ content:" - " }
.ds-quote-role:first-child::before{ content:"" }

/* 1. A rule down one side.
   2px, in the site's accent, and the gap scales with the text so it looks the
   same at every size rather than tight on a large quote and loose on a small
   one. */
.ds-quote-line{ display:flex; align-items:center; gap:0 }
.ds-quote-line.ds-qline-left { border-left:2px solid var(--ds-accent); padding-left:.7em }
.ds-quote-line.ds-qline-right{ border-right:2px solid var(--ds-accent); padding-right:.7em;
  text-align:right }
.ds-quote-line{ flex-direction:column; align-items:stretch }

/* 2. The marks themselves.
   Drawn as content on the wrapper, sized off the type scale, and set in the
   accent so they read as a mark rather than as more words. */
.ds-quote-marks{ position:relative }
/* The marks are set in the text font, not the heading font the quotation uses.
   A display face is drawn for its letters and its quote glyph is often an
   afterthought - a condensed heading font rendered the mark as a speck at the
   size it was asked for. The text font is the one face a site is guaranteed to
   have a proper mark in. */
/* Which face draws the mark, set by the element.
 *
 * The text font leads and is the default: on a geometric sans that is a squared
 * mark, cut from the same shapes as the rest of the page, which is the one that
 * belongs there. Serif draws the rounded comma shape - a different look, and an
 * option rather than a correction. */
.ds-qfont-serif   { --ds-qmark-font:Georgia,'Iowan Old Style','Times New Roman',Times,serif }
.ds-qfont-text    { --ds-qmark-font:var(--ds-font-text) }
.ds-qfont-heading { --ds-qmark-font:var(--ds-font-heading) }

.ds-quote-marks .ds-quote-text::before,
.ds-quote-marks .ds-quote-text::after{
  font-family:var(--ds-qmark-font, var(--ds-font-text));
  /* The quotation's own choice, then the accent it has always used. Fixed to
     the accent, a mark on a row that had been given its own colors was the one
     part of the quotation nobody could do anything about. */
  color:var(--ds-qmark-color, var(--ds-accent));
  line-height:.8; display:block }

/* The gap between a mark and the words, as a figure rather than as a guess.
 *
 * The eye measures ink to ink, and neither box is anywhere near its ink. A
 * quotation mark's ink sits high: at line-height .8 the box carries 0.51em of
 * empty space below the glyph. The words carry their own, and theirs changes
 * with the line-height the size class uses.
 *
 * So the margins are not the gap - they are the gap minus everything the boxes
 * already contribute, which is why they come out negative at the top. Measured
 * on Manrope at a 150px mark over 60px words: the marks sat 93px above and 81px
 * below the ink, for margins of 7.5px and 81.6px.
 *
 * Every term scales with its own font size, so this holds at any mark size and
 * any size class. It assumes the line-heights below, which are the defaults; a
 * theme that overrides --ds-h2-lh and the rest moves the words' ink and the gap
 * drifts by the difference. */
.ds-quote{ --ds-qgap:50px }

/* How far the words' ink sits inside their own line box, top and bottom.
 *
 * Two parts: the ink of the face itself, which is .15em from each edge of a
 * line box set solid, and half the leading, which is whatever the line-height
 * adds beyond the font size. One rule covers every size class because both
 * parts are read from the words themselves - em is their size and lh is their
 * line-height - rather than restated per class against a variable that only
 * says what the size was meant to be.
 *
 * Declared with @property so they are worked out here, on the words, and
 * inherit as plain pixels. An ordinary custom property is substituted as text,
 * so an em inside one would be resolved wherever it was used - and it is used
 * inside the mark, where an em is the mark's 150px and not the words' size at
 * all. That is exactly the fault this replaces: the figures were written per
 * size class against --ds-h3-size and --ds-h4-size, both of which are set in a
 * line-height of their own, so those two came out 5px loose. */
@property --ds-qink-top{ syntax:'<length>'; inherits:true; initial-value:0px }
@property --ds-qink-bottom{ syntax:'<length>'; inherits:true; initial-value:0px }

.ds-quote .ds-quote-text{
  --ds-qink-top:calc(.15em + (1lh - 1em) / 2);
  --ds-qink-bottom:calc(-.15em + (1lh - 1em) / 2);
}

.ds-quote-marks .ds-quote-text::before{
  content:var(--ds-qopen,'\201C');
  margin-bottom:calc(var(--ds-qgap,50px) - .51em - var(--ds-qink-top,0px));
}
/* Both marks follow the quotation's own alignment. The closing one was forced
   to the right, which on a centered quote threw it to the far edge of the column
   while the words sat in the middle - two things that plainly belong together,
   as far apart as the row allowed. */
.ds-qmarks-both .ds-quote-text::after{
  content:var(--ds-qclose,'\201D');
  margin-top:calc(var(--ds-qgap,50px) - var(--ds-qink-bottom,0px) - .0567em);
}

/* No guillemets. They are quotation marks in French and read as chevrons to
   everybody else, which is the one thing a quotation mark cannot afford to do. */
.ds-qglyph-double  { --ds-qopen:'\201C'; --ds-qclose:'\201D' }
.ds-qglyph-single  { --ds-qopen:'\2018'; --ds-qclose:'\2019' }
.ds-qglyph-straight{ --ds-qopen:'"';     --ds-qclose:'"' }
.ds-qglyph-block   { --ds-qopen:'\275D'; --ds-qclose:'\275E' }

/* The mark is the size it was asked for, in pixels.
 *
 * These were four rules borrowing the heading sizes, and a heading size is the
 * wrong number for a mark: a quotation glyph fills perhaps a third of its em
 * box, so the largest heading on a site drew a mark a third of that. The size
 * is its own figure now, written on the quotation. */
.ds-quote-marks .ds-quote-text::before,
.ds-quote-marks .ds-quote-text::after{ font-size:var(--ds-qmark-size,max(calc(150px * var(--ds-lo, .5333)), 7.8125vw)) }

/* And where they sit, when that is not where the words sit.
 *
 * Only written when an answer was given: with no class the marks go on taking
 * the quotation's own alignment, which is the behavior every quote on the site
 * already has. The pseudo-elements are display:block, so aligning them is
 * aligning their one glyph inside the full width of the quotation. */
.ds-qma-left   .ds-quote-text::before, .ds-qma-left   .ds-quote-text::after{ text-align:left }
.ds-qma-center .ds-quote-text::before, .ds-qma-center .ds-quote-text::after{ text-align:center }
.ds-qma-right  .ds-quote-text::before, .ds-qma-right  .ds-quote-text::after{ text-align:right }


/* ---- the Box element ---- */

/* A container with a look of its own. Everything it draws comes from the
   element's own settings as inline style, so this only has to give it a shape
   to hold and the two shadows the dropdown offers.

   No background, border or padding here on purpose: a box with nothing set is
   an invisible wrapper that groups its contents, and imposing a card on that
   would make the plain case impossible. */
/* A row inside a column.
 *
 * Twelve columns like the page's own rows, so a split written as 8-4 means the
 * same proportion here as it does out there. It is only ever one deep, so there
 * is no nesting to reason about: this grid never contains another one.
 *
 * One column on a phone. A third of a third of a screen is not a column. */
/* The gutter, but never more than the box can hold.
 *
 * Twelve tracks have eleven gaps between them, and an item spanning all twelve
 * measures the tracks plus every gap it crosses. At the page gutter of a wide
 * window that is 570px of gap inside a quarter-width column: the tracks
 * collapse to nothing, the item still measures 570, and it lies across its
 * neighbor. A heading sat over the picture in the next column at every window
 * wider than about 1400px.
 *
 * Three percent holds eleven gaps to a third of the box whatever the box is,
 * and at full page width it comes out at the gutter anyway. */
.ds-inrow{ display:grid; grid-template-columns:repeat(12,minmax(0,1fr));
  gap:min(var(--ds-gutter,24px), 3%); width:100%; min-width:0 }
.ds-inrow-top   { align-items:start }
.ds-inrow-middle{ align-items:center }
.ds-inrow-bottom{ align-items:end }

/* An in-row laid over a scene lets the pointer through to it.

   A scene3d is dragged, and the in-row is its sibling drawn after it, so it
   covers the whole column and takes every press before the canvas sees one -
   the bike simply would not turn anywhere the words were, which is most of
   the hero. The words themselves have nothing to do with a press.

   Only where a scene is actually present, so an ordinary in-row still
   selects and hovers as it did, and only the boxes themselves - anything in
   there that can be clicked, typed in or focused takes its presses back, so
   a button over a scene still works. */
.ds-col:has(> .ds-scene3d) > .ds-inrow{ pointer-events:none }
.ds-col:has(> .ds-scene3d) > .ds-inrow :is(a, button, input, select, textarea, label, summary, [tabindex], [data-ds-fx], .ds-btn){ pointer-events:auto }
.ds-inrow-col{ min-width:0 }
.ds-inrow-col > .ds-el:last-child{ margin-bottom:0 }

@media (max-width:640px){
  .ds-inrow{ grid-template-columns:1fr }
  .ds-inrow-col{ grid-column:1 / -1 !important }
}

/* The Box element.
   Nothing is written unless a color was chosen, so a box with no settings is
   still the invisible wrapper it has always been - the variables only exist
   when the Elements tab has something in them, and an element given its own
   color in the page builder writes it inline, which beats both. */
.ds-box{
  box-sizing:border-box;
  /* Nought unless the Elements screen says otherwise, which is the
     invisible wrapper a box with no settings has always been. A box given
     an edge takes the card padding instead - the rule below, which comes
     after this one and so wins where both apply. */
  padding:var(--ds-box-pt,0) var(--ds-box-pr,0)
          var(--ds-box-pb,0) var(--ds-box-pl,0);
  background:var(--ds-box-bg, transparent);
  color:var(--ds-box-text, inherit);
  border-color:var(--ds-box-line, currentColor);
}

/* A heading inside a box takes the box's own heading color.
 *
 * Set nothing and it keeps the site's, exactly as before. Give a box a dark
 * background though and the headings in it stayed the page's heading color
 * with no way to answer that short of writing CSS - the words below them had a
 * control and the headings did not. */
.ds-box :is(h1,h2,h3,h4,h5,h6){ color:var(--ds-box-head, inherit); }

/* A box that has been given an edge gets room inside it.
 *
 * Nothing set is still an invisible wrapper - that stays true. But a border or
 * a background is somebody saying "this is a card", and a card whose words
 * start on its own border is not a style anybody chose, it is a setting they
 * were expected to find. This is the theme's answer to a card, and typing a
 * padding on the element still beats it. */
.ds-box-card{
  padding:var(--ds-box-pt,26px) var(--ds-box-pr,28px)
          var(--ds-box-pb,26px) var(--ds-box-pl,28px) }

/* The last element inside a box has nothing following it, so the margin every
   element carries below itself would show as padding the box did not ask for. */
.ds-box > .ds-el:last-child{ margin-bottom:0 }

.ds-box-soft{ box-shadow:0 2px 10px rgba(0,0,0,.10) }
.ds-box-deep{ box-shadow:0 18px 50px rgba(0,0,0,.28) }

/* The two named colors, on words.
 *
 * One class per color and the tag decides what it paints: on a span it is the
 * text, on a mark it is the highlight behind it. Both come from the theme
 * settings, so changing a color there moves every word that uses it - and the
 * secondary color, which was written into every page and used by nothing until
 * now, finally does something.
 */
.ds-accent   { color:var(--ds-accent) }
.ds-secondary{ color:var(--ds-secondary) }

mark.ds-accent,
mark.ds-secondary{
  color:inherit;
  padding:.05em .2em;
}

mark.ds-accent   { background:var(--ds-accent, mark) }
mark.ds-secondary{ background:var(--ds-secondary, mark) }

/* The whole card is the link.
   The anchor sits around the title and is stretched over the card, so the
   picture and the empty parts of the box are clickable too - and there is still
   exactly one link per card, named by its title. */
.ds-post-card{ position:relative }

.ds-post-card-link::after{
  content:''; position:absolute; inset:0; z-index:1;
  border-radius:inherit;
}

/* Anything else in a card that is meant to be clicked sits over that layer. */
.ds-post-card a:not(.ds-post-card-link),
.ds-post-card button{ position:relative; z-index:2 }

/* Text stays selectable: the overlay swallows a drag, so the pointer says what
   it is doing. */
.ds-post-card{ cursor:pointer }
.ds-post-card:hover .ds-post-card-title{ color:var(--ds-link-hover, var(--ds-link)) }

/* Code, shown rather than run.
   Sunk into the page the way code is everywhere else, and left to scroll
   sideways by default: wrapping a line of markup mid-attribute makes it harder
   to read than a scrollbar does. */
/* Room under it, like every other element has.

   margin:0 meant whatever came next sat hard against the box - and what
   comes after a code block is nearly always the thing that code produces, so
   on the shortcodes page every live example was touching its own example. The
   element gap, so it matches the text and the headings around it rather than
   being a number picked for this one place.

   Not on the last one: there is nothing under it to be apart from. */
.ds-code{
  /* Its own reset, and the element gap under it - the same shorthand trap the
     list fell into. */
  margin:0 0 var(--ds-el-gap,50px);
  padding:14px 16px;
  overflow-x:auto;
  background:var(--ds-code-bg, color-mix(in srgb, var(--ds-text,#f5f5f5) 7%, transparent));
  border:1px solid var(--ds-code-line, color-mix(in srgb, var(--ds-text,#f5f5f5) 14%, transparent));
  border-radius:8px;
  color:var(--ds-code-ink, inherit);
  font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size:.875em; line-height:1.6;
  tab-size:2;
}

.ds-code code{
  display:block;
  background:none; padding:0; border:0;
  font:inherit; color:inherit;
  white-space:pre;
}

.ds-el.ds-code:not(:last-child){ margin-bottom:var(--ds-el-gap,20px) }

.ds-code-wrap code{ white-space:pre-wrap; overflow-wrap:anywhere }

/* ----------------------------------------------------------------- list ---
   Markers drawn by the list rather than by the browser's own bullet, so the
   gap between the mark and the words is a measurement rather than whatever
   the user agent happens to use - and so a marker can be an icon. */
/* ul.ds-list, not .ds-list.
   The theme lays prose lists out as a grid, and that rule is a tag plus a
   class - so a single class here lost to it and every item of a list sat in a
   row. Matched at the same weight, this wins by being later. */
ul.ds-list,
ol.ds-list{ display:block }

/* The reset keeps the gap under it.
 *
 * A list is a ul, so the browser's own margin has to go - but margin:0 is a
 * shorthand, it is written after .ds-el, and it took the standard element gap
 * with it. Every list in the suite has been sitting flush against whatever
 * follows it. Only the three sides that need resetting are reset. */
.ds-list{
  margin:0 0 var(--ds-el-gap,50px);
  padding:0;
  list-style:none;
  column-gap:var(--ds-gutter,24px);
  /* Elements > Lists > Text. The marker and the rule have had a color of their
     own for a while; the words never did, so a list could only ever be the
     color of the page around it. Blank inherits, which is what every list is
     already doing. */
  color:var(--ds-list-ink, inherit);
}

.ds-list li{
  position:relative;
  display:flex; align-items:baseline; gap:.6em;
  padding-left:0;
  margin-bottom:var(--ds-list-gap, .5em);
  /* A list in columns must not break an item across the gap. */
  break-inside:avoid;
}

.ds-list li:last-child{ margin-bottom:0 }

/* The bullet and the number are drawn as their own box, so a wrapped line
   indents under the words rather than under the mark. */
.ds-list-bullet li::before{
  content:'';
  flex:0 0 auto;
  width:.36em; height:.36em; margin-top:.55em;
  border-radius:50%;
  background:var(--ds-list-mark, var(--ds-counter-num, currentColor));
}

.ds-list-number{ counter-reset:ds-list }
.ds-list-number li{ counter-increment:ds-list }
/* The number takes the Number swatch on Elements, Cards, Text and marks -
   the same one the counter element uses, because they are the same thing
   drawn twice. A color set on the list itself still wins over it, and a
   list on a site that has set neither is the text color it stands in.

   No opacity. Seven tenths of a color somebody chose is a color they did
   not choose and cannot reach - a number meant to sit back can be given one
   that sits back. */
.ds-list-number li::before{
  content:counter(ds-list) '.';
  flex:0 0 auto;
  color:var(--ds-list-mark, var(--ds-counter-num, currentColor));
  font-variant-numeric:tabular-nums;
}

/* The icon keeps its own color where the Elements tab has set one, and takes
   the text color where it has not - the same rule every icon here follows. */
.ds-list-icon .ds-list-icon,
.ds-list .ds-list-icon{
  flex:0 0 auto;
  width:1em; height:1em;
  margin-top:.2em;
  color:var(--ds-list-mark, var(--ds-icon-ink, currentColor));
}

.ds-list-none li{ gap:0 }

/* The divider.
 *
 * A rule under each item and none under the last, so the list is separated
 * rather than boxed - "between items" means between them. The gap becomes
 * padding as well as margin, or the line would sit hard against the words
 * above it and float away from the ones below.
 *
 * Its color is the Elements tab's list mark where one is set, and otherwise
 * the same faint rule everything else on the page uses. */
.ds-list-ruled li{
  padding-bottom:var(--ds-list-gap, .5em);
  border-bottom:1px solid var(--ds-list-rule, var(--ds-line, color-mix(in srgb, currentColor 18%, transparent)));
}

.ds-list-ruled li:last-child{ border-bottom:0; padding-bottom:0 }

/* Above and below as well: the last item keeps its rule and the list takes one
   over the top of it. */
.ds-list-ruled-all{
  border-top:1px solid var(--ds-list-rule, var(--ds-line, color-mix(in srgb, currentColor 18%, transparent)));
  padding-top:var(--ds-list-gap, .5em);
}

.ds-list-ruled-all li:last-child{
  border-bottom:1px solid var(--ds-list-rule, var(--ds-line, color-mix(in srgb, currentColor 18%, transparent)));
  padding-bottom:var(--ds-list-gap, .5em);
}

/* In columns the rule belongs to the item, so each column carries its own -
   which is right, and is why the last item of a column keeps its line unless
   it is the last of the list. */

/* Centered and right aligned lists put the mark on the same side as the words,
   which is the only arrangement that reads as deliberate. */
.ds-list.ds-align-center li{ justify-content:center }
.ds-list.ds-align-right li{ justify-content:flex-end }



/* Category badges and tags on the builder's own post card. The theme styles
   both already; the card only has to give the badges something to sit on and
   keep the body a column so Read more lands under the excerpt. */
.ds-post-card-img { position: relative }
.ds-post-card-body { display: flex; flex-direction: column }
.ds-post-card-cats { margin: 4px 0 0; opacity: .8 }
.ds-post-card-body .ds-card-more { margin-top: auto; align-self: flex-start }

/* ---------------------------------------------------------------------
 * A row running a slideshow
 *
 * The slides stack in one box and only the showing one is drawn. Everything
 * here is written so that a browser with no JavaScript, or a reader who has
 * asked for less movement, still sees the first slide whole - the words are
 * in the markup and are laid out by CSS, not placed by a script.
 * ------------------------------------------------------------------ */
.ds-row-hasshow{ position:relative; overflow:hidden }

.ds-slideshow{
  position:absolute; inset:0; z-index:0;
  /* How far through the current slide's own screen of scrolling, nought to
     one. Written by the script; used by the wipe and the push. */
  --ds-slide-at:0;
}

/* Scoped to the slideshow it belongs to.
 *
 * Two different components own the class name .ds-slide: this one, where a
 * slide is stacked on its neighbours and fills the row, and the slider effect,
 * where a slide is a card in a track. Bare, each broke the other - the slider's
 * clamp made these 380px wide, and these absolute-and-zero-height rules made
 * the slider's cards invisible, which is exactly how the infinite slider demo
 * came to draw nothing at all.
 *
 * Both sides are scoped now. Neither name is claimed globally. */
.ds-slideshow > .ds-slide{
  position:absolute; inset:0;
  width:100%; height:100%;
  display:grid;
  opacity:0; visibility:hidden;
  transition:opacity .8s cubic-bezier(.2,0,.2,1);
}
.ds-slideshow > .ds-slide.is-on{ opacity:1; visibility:visible }

/* The picture or the video behind one slide. */
.ds-slide-media{
  position:absolute; inset:0;
  width:100%; height:100%;
  object-fit:cover;
  display:block;
}

/* The row's Dim, over one slide's picture and under its words. Opacity is
   written on the element, because it is the row's figure rather than a
   constant. */
.ds-slide-dim{
  position:absolute; inset:0; z-index:0;
  background:#000; pointer-events:none;
}

/* Words over a wash are white, whatever the page's own text color is. A dim
   is only ever asked for to make words readable over a picture, so the two
   go together - and on a light theme the words were the page's near black
   over a black wash.

   The heading is named as well as the wrapper. It carries the theme's own
   heading color rather than inheriting, so setting the wrapper turned the
   paragraph white and left the headline near black on black. */
.ds-slide:has(.ds-slide-dim) .ds-slide-words,
.ds-slide:has(.ds-slide-dim) .ds-slide-title,
.ds-slide:has(.ds-slide-dim) .ds-slide-sub,
.ds-slide:has(.ds-slide-dim) .ds-slide-btn{ color:#fff }

/* ---- where the words sit ---- */

.ds-slide-words{
  position:relative; z-index:1;
  display:grid; gap:max(12px, min(1.6vw, 22px), 1.1458vw);
  align-content:center;
  padding:max(24px, min(6vw, 90px), 4.6875vw);
  max-width:min(46rem, 90%);
}

.ds-slides-hleft   .ds-slide-words{ justify-self:start;  text-align:left }
.ds-slides-hcenter .ds-slide-words{ justify-self:center; text-align:center }
.ds-slides-hright  .ds-slide-words{ justify-self:end;    text-align:right }

.ds-slides-vtop    .ds-slide-words{ align-self:start }
.ds-slides-vmiddle .ds-slide-words{ align-self:center }
.ds-slides-vbottom .ds-slide-words{ align-self:end }

/* A button is not a block, so it does not stretch the width of the words. */
.ds-slides-hleft   .ds-slide-btn{ justify-self:start }
.ds-slides-hcenter .ds-slide-btn{ justify-self:center }
.ds-slides-hright  .ds-slide-btn{ justify-self:end }

/* ---- the parts ---- */

/* What a masked part moves inside. Clipping is the whole effect: the line is
   uncovered rather than flown in, so it needs a box with an edge. */
.ds-slide-mask{ display:block; overflow:hidden }

.ds-slide-title{ margin:0; line-height:1.05 }
.ds-slide-sub{ margin:0; opacity:.86 }

/* ---- how one background becomes the next ---- */

/* Crossfade is the plain one and is already what .ds-slide does. */

/* Pushed: the new slide arrives from the edge and the old one leaves. Driven
   by the same figure the script writes, so it follows the pointer. */
.ds-slideshow-push .ds-slide{ transition:none; transform:translate3d(0,100%,0) }
.ds-slideshow-push .ds-slide.is-on{ transform:translate3d(0,0,0) }

/* Wiped: the incoming slide is revealed by a moving edge rather than faded.
   clip-path rather than a mask image, so there is nothing to load. */
.ds-slideshow-wipe .ds-slide{ transition:none; clip-path:inset(100% 0 0 0) }
.ds-slideshow-wipe .ds-slide.is-on{
  clip-path:inset(calc((1 - var(--ds-slide-at,1)) * 0%) 0 0 0);
}

/* Zoomed: the outgoing picture grows and the incoming one settles back. */
.ds-slideshow-zoom .ds-slide{ transform:scale(1.12) }
.ds-slideshow-zoom .ds-slide.is-on{
  transform:scale(1);
  transition:opacity .8s cubic-bezier(.2,0,.2,1), transform 1.2s cubic-bezier(.2,0,.2,1);
}

/* ---- the dots ---- */

/* Where the markers sit, across and down - set on the row, the same two
   answers the words take. Inset from the edge by the same figure whichever
   corner they are in, so moving them never changes how far in they are. */
.ds-slide-dots{
  position:absolute; z-index:2;
  --ds-pip-inset:max(16px, min(3vw, 34px), 1.7708vw);
  display:flex; gap:10px;
}

.ds-pips-hleft   { left:var(--ds-pip-inset); justify-content:flex-start }
.ds-pips-hcenter { left:0; right:0; justify-content:center }
.ds-pips-hright  { right:var(--ds-pip-inset); justify-content:flex-end }

.ds-pips-vtop    { top:var(--ds-pip-inset) }
.ds-pips-vbottom { bottom:var(--ds-pip-inset) }
/* Down the middle, and up the side rather than across it - a vertical strip
   is what a marker set in the middle of an edge wants to be. */
.ds-pips-vmiddle { top:50%; transform:translateY(-50%); flex-direction:column }

.ds-slide-dot{
  width:9px; height:9px; padding:0;
  border:0; border-radius:999px;
  background:rgba(255,255,255,.4);
  cursor:pointer;
  transition:background .3s, transform .3s;
}
.ds-slide-dot.is-on{ background:#fff; transform:scale(1.35) }

/* A slideshow is a picture with words over it, and a phone turns the picture
   into a tall crop - so the words need room rather than the middle of it. */


/* Asked to keep still: the first slide, and nothing moving. */
@media (prefers-reduced-motion: reduce){
  .ds-slide{ transition:none }
  .ds-slideshow-push .ds-slide,
  .ds-slideshow-zoom .ds-slide{ transform:none }
  .ds-slideshow-wipe .ds-slide{ clip-path:none }
}

/* ---------------------------------------------------------------------
 * Scrubbed: the scroll is the playhead
 *
 * Every slide is stacked and visible, and a GSAP timeline decides what is
 * seen at each position. The stylesheet must not move the same properties -
 * a CSS transition and a scrubbed tween on one property is the two of them
 * fighting frame by frame, which reads as a stutter.
 * ------------------------------------------------------------------ */
.ds-slideshow.is-scrub .ds-slide{
  transition:none;
  visibility:visible;
}

/* Each way of changing, handed over to the timeline. */
.ds-slideshow.is-scrub.ds-slideshow-push .ds-slide,
.ds-slideshow.is-scrub.ds-slideshow-zoom .ds-slide{ transform:none }
.ds-slideshow.is-scrub.ds-slideshow-wipe .ds-slide,
.ds-slideshow.is-scrub.ds-slideshow-wipe .ds-slide.is-on{ clip-path:none }

/* ---- what marks where you are ---- */

/* Numbers say how many there are, which a row of identical dots never does. */
.ds-slide-dots-numbers .ds-slide-dot{
  width:auto; height:auto;
  min-width:26px; padding:3px 7px;
  border-radius:4px;
  font:600 12px/1.4 inherit;
  color:rgba(255,255,255,.7);
  background:rgba(255,255,255,.14);
}
.ds-slide-dots-numbers .ds-slide-dot.is-on{
  color:#000; background:#fff; transform:none;
}

.ds-slide-dots-squares .ds-slide-dot{ border-radius:2px; width:10px; height:10px }

/* A strip that reads as progress rather than as a set of buttons. */
.ds-slide-dots-bars .ds-slide-dot{
  width:34px; height:3px; border-radius:0;
}
.ds-slide-dots-bars .ds-slide-dot.is-on{ transform:none }

/* Blinds: bands that grow rather than one edge that travels. No single
   clip-path can describe a row of separate slats, so this is a repeating mask
   whose black bands widen from nothing to the full slat - driven by one custom
   property the timeline animates. */
.ds-slideshow-blinds .ds-slide{
  --ds-blind:0%;
  -webkit-mask-image:repeating-linear-gradient(to right,
    #000 0, #000 var(--ds-blind), transparent var(--ds-blind), transparent 12.5%);
  mask-image:repeating-linear-gradient(to right,
    #000 0, #000 var(--ds-blind), transparent var(--ds-blind), transparent 12.5%);
}
.ds-slideshow-blinds .ds-slide:first-child{ --ds-blind:12.5% }

/* ---------------------------------------------------------------------
 * Tabs and accordions
 *
 * One set of rules for both. They are the same thing shown two ways: named
 * panels, one or more of them open. Everything a site can set arrives as a
 * variable, so this file states shapes and never colors.
 * ------------------------------------------------------------------ */
.ds-panels{ --ds-tab-radius:0px; --ds-tab-bw:1px }

/* Two edges, drawn from two figures. Where a site sets only one color the
   other falls back to it, so one border is one setting and two is two. */
.ds-tabs-look-filled .ds-tab{
  border-color:var(--ds-tab-line, transparent);
}
.ds-tab-bodies{
  border:var(--ds-tab-bw) solid var(--ds-tab-bline, var(--ds-tab-line, transparent));
}
/* A plain set has no box round its body - the rule under the strip is the
   whole device, and a border round the content turns it into a card. */
.ds-tabs-look-plain .ds-tab-bodies{ border:0 }
.ds-panels-accordion .ds-tab-bodies{ border:0 }
.ds-panels-accordion .ds-tab{ border:0 }

.ds-tab-strip{ display:flex; flex-wrap:wrap; gap:2px }

.ds-tab{
  display:inline-flex; align-items:center; gap:.5em;
  /* No side padding by default - the rule under a tab runs the full width
     of the element, so 18px at each end set every question in from its own
     line. Vertical air only; the sides are the row's gutter. */
  padding:var(--ds-tab-pad, 12px 0);
  /* The width comes from the element; the color is set per look below. */
  border:var(--ds-tab-bw) solid transparent; background:none; cursor:pointer;
  font:inherit; text-align:left;
  color:var(--ds-tab-ink, inherit);
  border-radius:var(--ds-tab-radius) var(--ds-tab-radius) 0 0;
}
.ds-tab.is-on{ color:var(--ds-tab-ink-on, inherit) }

/* The chosen text style. A class rather than a real heading tag - a strip of
   six h2s is a document outline nobody meant to write. */
.ds-tab-h1 span{ font-size:var(--ds-h1-size, 2.4em) }
.ds-tab-h2 span{ font-size:var(--ds-h2-size, 2em) }
.ds-tab-h3 span{ font-size:var(--ds-h3-size, 1.6em) }
.ds-tab-h4 span{ font-size:var(--ds-h4-size, 1.3em) }
.ds-tab-h5 span{ font-size:var(--ds-h5-size, 1.1em) }
.ds-tab-h6 span{ font-size:var(--ds-h6-size, 1em) }

/* Plain: a rule under the strip and a mark under the open one. */
.ds-tabs-look-plain .ds-tab-strip{
  border-bottom:1px solid var(--ds-tab-line, var(--ds-line, rgba(128,128,128,.3)));
}
/* The mark under the open tab. A strip only - an accordion has no strip, and
   the same rule there drew a bright line under whichever header was open,
   in currentColor, which on an open header is the text color. */
.ds-tabs-look-plain:not(.ds-panels-accordion) .ds-tab{ margin-bottom:-1px; border-bottom:2px solid transparent }
.ds-tabs-look-plain:not(.ds-panels-accordion) .ds-tab.is-on{ border-bottom-color:var(--ds-tab-ink-on, currentColor) }

/* Filled: the open tab and the body are one shape, which is what makes a tab
   read as a folder rather than as a row of links. */
.ds-tabs-look-filled .ds-tab{ background:var(--ds-tab-bg, rgba(128,128,128,.12)) }
.ds-tabs-look-filled .ds-tab.is-on{ background:var(--ds-tab-bg-on, var(--ds-card-bg, #222)) }
.ds-tabs-fill .ds-tab-bodies{ background:var(--ds-tab-bg-on, var(--ds-card-bg, #222)) }

.ds-tab-body{ padding:var(--ds-tab-bodypad, 20px 0) }
.ds-tab-body[hidden]{ display:none }

/* ---- where the strip sits ---- */

.ds-tabs-at-left,
.ds-tabs-at-right{ display:flex; align-items:flex-start; gap:0 }
.ds-tabs-at-left .ds-tab-strip,
.ds-tabs-at-right .ds-tab-strip{ flex-direction:column; flex:0 0 auto }
.ds-tabs-at-left .ds-tab-bodies,
.ds-tabs-at-right .ds-tab-bodies{ flex:1 1 auto; min-width:0 }
.ds-tabs-at-right{ flex-direction:row-reverse }
.ds-tabs-at-bottom{ display:flex; flex-direction:column-reverse }

/* Down one side the tab's corners and its mark turn with it. */
.ds-tabs-at-left .ds-tab,
.ds-tabs-at-right .ds-tab{ border-radius:var(--ds-tab-radius) }
.ds-tabs-look-plain.ds-tabs-at-left .ds-tab-strip,
.ds-tabs-look-plain.ds-tabs-at-right .ds-tab-strip{ border-bottom:0 }
.ds-tabs-look-plain.ds-tabs-at-left .ds-tab-strip{
  border-right:1px solid var(--ds-tab-line, var(--ds-line, rgba(128,128,128,.3)));
}
.ds-tabs-look-plain.ds-tabs-at-right .ds-tab-strip{
  border-left:1px solid var(--ds-tab-line, var(--ds-line, rgba(128,128,128,.3)));
}

/* ---- how the tabs line up ---- */

.ds-tabs-align-center .ds-tab-strip{ justify-content:center }
.ds-tabs-align-right  .ds-tab-strip{ justify-content:flex-end }
.ds-tabs-align-stretch .ds-tab-strip .ds-tab{ flex:1 1 0; justify-content:center }

/* ---- how a panel arrives ---- */

.ds-tabs-anim-fade .ds-tab-body.is-on{ animation:ds-tab-fade .35s cubic-bezier(.2,0,.2,1) both }
@keyframes ds-tab-fade{ from{ opacity:0 } to{ opacity:1 } }

.ds-tabs-anim-slide .ds-tab-body.is-on{ animation:ds-tab-slide .4s cubic-bezier(.2,0,.2,1) both }
@keyframes ds-tab-slide{ from{ opacity:0; transform:translateY(10px) } to{ opacity:1; transform:none } }

@media (prefers-reduced-motion: reduce){
  .ds-tab-body.is-on{ animation:none }
}

/* ---- an accordion ---- */

/* The rule between one fold and the next - the accordion's only line, and
   what Tab border sets. */
.ds-panels-accordion .ds-tab-fold{
  border-bottom:var(--ds-tab-bw) solid var(--ds-tab-line, var(--ds-line, rgba(128,128,128,.3)));
}

/* Nothing under the last one: a list of folds should not end in a rule with
   nothing below it. */
.ds-panels-accordion .ds-tab-fold:last-child{ border-bottom:0 }
.ds-panels-accordion .ds-tab{ width:100%; justify-content:space-between }

/* The marker, drawn rather than loaded - a chevron is two lines and a plus is
   two more, and neither is worth a font. */
.ds-panels-accordion .ds-tab::after{
  content:'';
  width:.6em; height:.6em; flex:0 0 auto;
  transition:transform .3s cubic-bezier(.2,0,.2,1);
}
.ds-acc-mark-chevron .ds-tab::after{
  border-right:2px solid currentColor; border-bottom:2px solid currentColor;
  transform:rotate(45deg); margin-top:-.2em;
}
.ds-acc-mark-chevron .ds-tab.is-on::after{ transform:rotate(-135deg); margin-top:.2em }
.ds-acc-mark-arrow .ds-tab::after{
  border-right:2px solid currentColor; border-bottom:2px solid currentColor;
  transform:rotate(-45deg);
}
.ds-acc-mark-arrow .ds-tab.is-on::after{ transform:rotate(45deg) }
.ds-acc-mark-plus .ds-tab::after{
  background:
    linear-gradient(currentColor,currentColor) center/100% 2px no-repeat,
    linear-gradient(currentColor,currentColor) center/2px 100% no-repeat;
}
.ds-acc-mark-plus .ds-tab.is-on::after{ background-size:100% 2px, 0 100% }
.ds-acc-mark-none .ds-tab::after{ display:none }

/* On the left it goes first, and the name takes the room instead. */
.ds-acc-at-left .ds-tab{ flex-direction:row-reverse; justify-content:flex-end }
.ds-acc-at-left .ds-tab span{ margin-right:auto }

/* ---- a folded set of tabs ---- */

/* Below the phone width the strip is put away and each name sits on its own
   panel. The markup does not change; this is the same set, folded. */
.ds-panels.is-folded .ds-tab-strip{ display:none }
.ds-panels.is-folded .ds-tab-bodies{ display:block }

/* ---- where you are ---- */

.ds-tab-pips{ display:flex; gap:8px; justify-content:center; margin-top:14px }
.ds-tab-pip{
  width:10px; height:10px; padding:0; border:0; cursor:pointer;
  background:var(--ds-tab-line, rgba(128,128,128,.4));
  border-radius:999px;
  transition:background .3s, transform .3s;
}
.ds-tab-pips-square .ds-tab-pip{ border-radius:2px }
.ds-tab-pips-lines  .ds-tab-pip{ width:28px; height:3px; border-radius:0 }
.ds-tab-pips-number .ds-tab-pip{
  width:auto; height:auto; min-width:24px; padding:2px 6px;
  border-radius:3px; font:600 12px/1.4 inherit;
  color:var(--ds-tab-ink, inherit);
}
.ds-tab-pip.is-on{ background:var(--ds-tab-ink-on, currentColor); transform:scale(1.2) }
.ds-tab-pips-lines .ds-tab-pip.is-on,
.ds-tab-pips-number .ds-tab-pip.is-on{ transform:none }

/* Where a name sits inside its own tab.
 *
 * A different question from where the tabs sit in the strip: a stretched tab
 * is wide, and what its label does with that width is the thing people are
 * actually looking at. An accordion header is always full width, so this is
 * the only control it has.
 */
.ds-tablabel-left   .ds-tab{ justify-content:flex-start; text-align:left }
.ds-tablabel-center .ds-tab{ justify-content:center; text-align:center }
.ds-tablabel-right  .ds-tab{ justify-content:flex-end; text-align:right }

/* The marker keeps its own edge whatever the words do - it belongs to the row,
   not to the label, and a plus sliding about with the text reads as broken. */
.ds-panels-accordion .ds-tab{ position:relative; padding-right:2em }
.ds-panels-accordion.ds-acc-at-left .ds-tab{ padding:0 0 0 2em }
.ds-panels-accordion .ds-tab::after{ position:absolute; right:0; top:50% }
.ds-acc-at-left .ds-tab::after{ right:auto; left:0 }
.ds-acc-mark-chevron .ds-tab::after{ transform:translateY(-70%) rotate(45deg) }
.ds-acc-mark-chevron .ds-tab.is-on::after{ transform:translateY(-30%) rotate(-135deg) }
.ds-acc-mark-arrow .ds-tab::after{ transform:translateY(-50%) rotate(-45deg) }
.ds-acc-mark-arrow .ds-tab.is-on::after{ transform:translateY(-50%) rotate(45deg) }
.ds-acc-mark-plus .ds-tab::after{ transform:translateY(-50%) }

/* The drawing sits on the text baseline rather than floating above it. */
.ds-tab-icon{ display:inline-flex; align-items:center; flex:0 0 auto }
.ds-tab-icon svg{ display:block }

/* A question wrapped in a real heading.
 *
 * The heading is the place in the document; the button inside it is the
 * control. It must add no space of its own or the accordion grows a gap per
 * row that no setting accounts for. */
/* The heading keeps its own type.

   This set font-family, font-size and font-weight to inherit, and the class
   is on the heading itself - so choosing h6 for a tab gave the tag without
   any of the typography, and the question rendered as body text. The button
   inside still inherits, which is what keeps a button from looking like one.
 */
/* No margin, and no opinion about type - see the note above the element. */
.ds-tab-head{ margin:0 }

/* The button takes its type from the heading it sits in, so a tab styled
   large or h6 looks that way while still being a control. */
.ds-tab-head .ds-tab{ width:100%; font:inherit; color:inherit; letter-spacing:inherit; text-transform:inherit }

/* The Sticky element.
 *
 * One property does all of it. The two things worth knowing are written here
 * rather than left to be discovered:
 *
 * align-self:start - a sticky child of a grid or flex column is stretched to
 * the full height of that column by default, and something already as tall as
 * the space it could travel through never appears to move at all. This is the
 * single most common reason sticky "does not work".
 *
 * It is released where its parent ends. That is how sticky is defined, and it
 * is also the behavior wanted: a summary beside an article should stop when
 * the article does rather than follow into the footer.
 */
.ds-sticky{
  position:sticky;
  top:var(--ds-stick-top,0px);
  align-self:start;
}

/* Where the script has taken over.
 *
 * The script holds the element's line itself, so the resting offset above is
 * its answer to a question already answered - and worse, top on a relative
 * element is not an offset from the screen but a shove down the page, which is
 * what put a header's height of empty space above every sticky element. This is
 * the only place either of them is decided: sticky on its own where there is no
 * GSAP, and the script wherever there is. */
.ds-sticky.is-pinned{ position:relative; top:auto }

/* Off where a column is the full width of the screen: a thing that stays put
   there covers what is being read.

   1024, not 640. The setting is called "Disable on tablet / mobile" and 640 is
   a phone - so a tablet, which is 768 to 1024, stayed above the line and the
   element carried on sticking on exactly the screens the setting names. Same
   figure sticky.js uses, or the two would disagree about what a tablet is. */
@media (max-width:1024px){
  .ds-sticky-desk,
  .ds-sticky-desk.is-pinned{ position:static }
}


/* Tabs and accordions, dressed from Elements > Tabs & Accordions.
   Each falls back to what the element did before, so a site that has set
   none of them is unchanged. */
.ds-tab{ background:var(--ds-tab-bg, none) }
.ds-tab.is-on{ background:var(--ds-tab-bg-on, var(--ds-tab-bg, none)) }
.ds-tab span{ font-size:var(--ds-tab-size, inherit) }
.ds-tab-body{
  background:var(--ds-tab-body-bg, transparent);
  color:var(--ds-tab-body-ink, inherit);
  font-size:var(--ds-tab-body-size, inherit);
}

/* ==============================================================
 * Bento rows
 *
 * A column layout gives every column the same height, which is what makes it a
 * row of columns. A bento gives each tile a width AND a height in the same
 * twelfths, so the row becomes a packed set of tiles of different sizes.
 *
 * The grid underneath is the one the rows already use, so nothing here fights
 * it: twelve tracks across, and rows of a set height added as needed.
 * ============================================================== */
.ds-row-bento > .ds-row-in{
  /* A height for a row to be a multiple of. Without one, "two rows tall" has
     nothing to be twice as tall as - grid sizes rows to their contents and two
     tiles of different content give two different answers. minmax lets a tile
     grow past it rather than clip, which matters the day somebody puts four
     paragraphs in one. */
  grid-auto-rows: minmax(var(--ds-bento-unit, 220px), auto);

  /* The same gutter both ways. A bento reads as a bento because the space
     between tiles is even; a column gap alone leaves the rows touching. */
  gap: var(--ds-col-mx, 24px);
}

/* Tiles fill their cell rather than sitting at the top of it - but only while
   nothing has asked them to sit somewhere.
 *
 * height:100% was written on every tile unconditionally, and a box with an
 * explicit height fills its cell whatever align-items says. So choosing Top,
 * Middle or Bottom did nothing at all: the control was there, it saved, and the
 * tiles stayed full height with nothing to say why.
 *
 * Vertical alignment is the control that places a tile in its cell on a bento,
 * so the full height only applies while that is left at the default. */
.ds-row-bento > .ds-row-in > .ds-col{ min-width: 0 }
.ds-row-bento.ds-cols-stretch.ds-row-vmiddle > .ds-row-in > .ds-col{ height: 100% }

/* Where a tile sits in its cell.
 *
 * The row's Vertical alignment, which on a full screen row moves the whole block
 * of columns within the window. A bento has the same question one level down: a
 * tile shorter than its grid cell can sit at the top of it, in the middle, or at
 * the bottom. Same control, same words, answered where it means something.
 *
 * Middle is the stored default for rows that never set it, so it keeps the old
 * behavior of tiles filling their cells - only an explicit Top or Bottom
 * changes what a page already looked like. */
.ds-row-bento.ds-row-vtop    > .ds-row-in{ align-items: start }
.ds-row-bento.ds-row-vbottom > .ds-row-in{ align-items: end }
.ds-row-bento.ds-row-vtop    > .ds-row-in > .ds-col,
.ds-row-bento.ds-row-vbottom > .ds-row-in > .ds-col{ height: auto }

/* How many rows a tile stands. Written out rather than generated, because the
   shapes only ever ask for these. */
.ds-row-bento > .ds-row-in > .ds-col-t2{ grid-row: span 2 }
.ds-row-bento > .ds-row-in > .ds-col-t3{ grid-row: span 3 }
.ds-row-bento > .ds-row-in > .ds-col-t4{ grid-row: span 4 }

/* Where a Swiss tile sits.
 *
 * The one thing this grid could not do before: a tile that starts at a named
 * column rather than packing against the one before it. The empty columns
 * either side are not columns, they are the grid showing through, and that is
 * the whole of the style - structural white space, placed on purpose.
 *
 * The figures arrive as variables on the element so this stays a class rule
 * that a media query can still overrule. Written inline as placement it could
 * not be, and all eighteen would hold their desktop shape on a phone.
 *
 * Both fall back to auto, so a tile naming no start simply flows and a Swiss
 * row half rebuilt by hand behaves like any other row. */
.ds-row-swiss > .ds-row-in > .ds-col-at{
  grid-column: var(--ds-at, auto) / span var(--ds-span, 12);
}
.ds-row-swiss > .ds-row-in > .ds-col-at[style*="--ds-atrow"]{
  grid-row: var(--ds-atrow, auto) / span var(--ds-down, 1);
}

/* The rail that holds is marked .ds-sticky by the renderer and driven by
   sticky.js, the same as every other sticky thing on a page. It needs only one
   thing here: a grid item stretches to its cell by default, so a sticky one is
   already as tall as the space it could travel through and never appears to
   move. */
.ds-row-swiss-rail > .ds-row-in > .ds-col-at.ds-sticky{ align-self: start }

/* Below the tablet breakpoint the row is already one column wide, so a tile
   that stands two rows would be two screens tall for no reason. Heights are
   given up with the widths. */
@media (max-width: 1024px){
  .ds-row-bento > .ds-row-in{ grid-auto-rows: auto }
  .ds-row-bento > .ds-row-in > .ds-col,
  .ds-row-bento > .ds-row-in > .ds-col-t2,
  .ds-row-bento > .ds-row-in > .ds-col-t3,
  .ds-row-bento > .ds-row-in > .ds-col-t4{
    grid-row: auto;
    height: auto;
  }

  /* A placed tile gives up its placement along with everything else. The void
     it held open has nowhere to go in one column, and a tile still starting at
     column nine on a grid one column wide does not draw at all. */
  .ds-row-swiss > .ds-row-in > .ds-col-at,
  .ds-row-swiss > .ds-row-in > .ds-col-at[style*="--ds-atrow"]{
    grid-column: 1 / -1;
    grid-row: auto;
  }

  /* And the rail stops holding. On one column it would pin a block over the
     content it was meant to sit beside. sticky.js reads the same breakpoint
     from the desk flag on the element; this covers the CSS fallback. */
  .ds-row-swiss-rail > .ds-row-in > .ds-col-at.ds-sticky{
    position: static;
  }

  /* Except where there is something behind to see.
   *
   * Giving the heights up leaves a tile exactly as tall as the three words in
   * it, and a photograph in a band that thin is a stripe rather than a picture.
   * A floor rather than a height, so a tile with more in it still grows.
   *
   * Any tile carrying a background of its own, not only the ones holding a
   * picture. A tile is a shape somebody chose - the color, the gradient, the
   * card - and three words on a colored band a hundred and forty pixels tall
   * has stopped being a tile and become a stripe, the same way the photograph
   * did. A tile with no background at all is not drawing a shape and loses
   * nothing by being the size of its contents, which is what it wants to be,
   * so it is the one case left out. */
  .ds-row-bento > .ds-row-in > .ds-col:has(> .ds-col-pic),
  .ds-row-bento > .ds-row-in > .ds-col:has(> .ds-col-vid),
  .ds-row-bento > .ds-row-in > .ds-col:has(> .ds-col-model),
  .ds-row-bento > .ds-row-in > .ds-col:has(> .ds-col-fill),
  .ds-row-bento > .ds-row-in > .ds-col.ds-col-bg,
  .ds-row-bento > .ds-row-in > .ds-col.ds-col-grad{
    min-height: min(var(--ds-bento-unit, 220px), 56vw);
  }
}

/* ==============================================================
 * A column as a surface
 *
 * A column was a div with a width. It can now carry a color, a gradient, a
 * picture, footage or a turnable model, with its own padding and its own idea
 * of where its contents sit - which is what a bento tile has to be, and what a
 * plain column benefits from just as much.
 *
 * The corner is --ds-card-radius, set once in Theme Settings. Nothing here
 * chooses one: a tile turning a different corner from every other card on the
 * site is not a decision anybody makes on purpose.
 * ============================================================== */
.ds-col-bg,
.ds-col-grad,
.ds-col-surface,
.ds-col-line,
.ds-col-pad,
.ds-col:has(> .ds-col-pic),
.ds-col:has(> .ds-col-vid),
.ds-col:has(> .ds-col-model){
  position: relative;
  border-radius: var(--ds-card-radius, 12px);
}

/* The tile clips what is behind, never what is in it.
 *
 * A radius clips an element's own background on its own - overflow is only ever
 * needed for children, and the only children that need it are the layers. Put
 * on the column instead, it also cut off everything the author had put in the
 * tile, which is what made a picture poking out of the top impossible.
 *
 * So each layer clips itself and the column is left open, and a breakout is
 * then a negative margin on the picture and nothing more. */
.ds-col-pic,
.ds-col-vid,
.ds-col-model{
  overflow: hidden;
  border-radius: inherit;
}

/* Except when the tile is asked to hold its contents in.
 *
 * Wanted where a tile is a frame around a photograph rather than a shape a
 * design breaks out of - and it has to be said, because the two are opposite
 * and nothing about a column tells you which one it is. */
.ds-col-clip{ overflow: hidden }

.ds-col-bg{ background-color: var(--ds-colbg) }
.ds-col-grad{ background-image: var(--ds-colgrad) }

/* What is written on the tile, whatever that is - a heading, the words, a link,
   a list marker. Headings and links each carry their own color from the theme,
   so they are named: inheriting alone left a heading black on a black tile. */
.ds-col-ink,
.ds-col-ink :is(h1, h2, h3, h4, h5, h6, p, li, blockquote, figcaption, strong, em, small){
  color: var(--ds-colink);
}
/* A link keeps being a link - underlined, and still its own color on hover -
   but it is legible against the tile it is on. */
.ds-col-ink a:not(.ds-btn){ color: var(--ds-colink) }

/* A surface with nothing else asked of it still reads as a tile.
   Column background in Settings > General > Swiss layouts first, then the card
   color, so it follows the palette rather than being a gray of its own on a site
   that has set neither. */
.ds-col-surface{ background-color: var(--ds-swiss-col-bg, var(--ds-card-bg, rgba(127,127,127,.08))) }
.ds-col-line{ border: var(--ds-card-bw, 1px) solid var(--ds-line, rgba(127,127,127,.28)) }

/* What a column is padded by when it has not been told.
 *
 * Nothing, for an ordinary column - it usually has no background and sits in a
 * row that already has gutters. But a bento tile is a surface with a color or a
 * picture running to its own edge, so it has to be inset or the words sit on the
 * seam. There was no default at all, which meant setting the same figure by hand
 * on every tile of every bento - and since a cut-out inherits the tile's
 * padding, a tile that had never been given one cut nothing.
 *
 * Written as a fallback rather than as a competing rule. --ds-colpad is only
 * emitted when a tile has its own padding, so this resolves to the tile's figure
 * where there is one and to the Elements, Bento figure where there is not. No
 * specificity to get wrong: the override wins by existing. */
.ds-col-in{ --ds-pad-base: 0px }
.ds-row-bento > .ds-row-in > .ds-col > .ds-col-in{ --ds-pad-base: var(--ds-bento-pad, 20px) }

/* A column carrying a fill needs room inside it, and one carrying nothing does
   not: with no edge behind the words there is nothing to push them away from,
   and padding it would move the text off the grid the whole page is set on.
   So the base figure follows the fill rather than the column.
 *
 * Column padding in Settings > General > Swiss layouts. A tile in a bento keeps
   the Elements figure above, which is why that rule stays and this one only
   reaches columns outside one. Padding set on the column still wins by
   existing, the same as everywhere else. */
.ds-col-surface > .ds-col-in,
.ds-col-bg > .ds-col-in,
.ds-col-grad > .ds-col-in,
.ds-col-line > .ds-col-in{ --ds-pad-base: var(--ds-swiss-col-pad, max(24px, 1.25vw)) }
.ds-col-in{ padding: var(--ds-colpad, var(--ds-pad-base)) }
.ds-col-pad > .ds-col-in{ padding: var(--ds-colpad) }

/* Room outside the tile. Written on the tile, not on the well inside it, so the
   gap falls between two tiles rather than inside both of them. */
.ds-col-mar{ margin: var(--ds-colmar) }

/* The layers behind the content. Absolute so they fill the tile whatever is in
   it, and under the content rather than over it. */
.ds-col-pic,
.ds-col-vid,
.ds-col-model{
  position: absolute;
  inset: 0;
  z-index: 0;
  display: block;
  background-repeat: no-repeat;
  pointer-events: none;
}

.ds-col-vid video{
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}

/* The model is turnable, so it is the one layer that takes the pointer. */
.ds-col-model{ pointer-events: auto; cursor: grab }
.ds-col-model:active{ cursor: grabbing }
.ds-col-model canvas{ width: 100%; height: 100%; display: block }

/* Anything over the background sits above it. */
.ds-col-in{ position: relative; z-index: 1; height: 100% }

/* Letting the drag reach the model underneath.
 *
 * The content wrapper is the full height of the tile and sits over every layer,
 * so it caught the pointer across the whole frame and the model could never be
 * taken hold of - the cursor said grab and nothing turned. Only the wrapper
 * steps aside: everything actually in it takes the pointer back, so the words
 * are still selectable and a button in a tile still a button. The empty space
 * around them is what the drag goes through.
 *
 * Only on a tile that has a model. Anywhere else there is nothing underneath
 * worth reaching and this would be machinery for no reason. */
.ds-col:has(> .ds-col-model) > .ds-col-in{ pointer-events: none }
.ds-col:has(> .ds-col-model) > .ds-col-in > *{ pointer-events: auto }

/* What is behind, held back so what is over it can be read. A layer of the page
   color rather than black, so it works whichever way round the scheme is. */
.ds-col-dimmed > .ds-col-pic::after,
.ds-col-dimmed > .ds-col-vid::after,
.ds-col-dimmed::before{
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: var(--ds-bg, #000);
  opacity: var(--ds-coldim, 0);
  pointer-events: none;
}

/* Where the content sits, said by the tile rather than by the row.
 *
 * The row's Column height still decides for any column that has not been asked
 * - this only speaks for the ones that have, which is what lets one tile in a
 * bento center its contents while its neighbor keeps them at the top. */
/* The gap below an element is the gap to the next one, so the last one has none.
 *
 * Every other container here already says this - a box, a nested row's column,
 * a text element, a quote - and a page column was the one that did not. So the
 * last element in every column carried fifty pixels of margin under it against
 * the bottom padding, which read as a column padded far more at the bottom than
 * the top.
 *
 * It is also what stopped Vertical alignment working. Bottom aligning packs the
 * contents to the end of the box, and that phantom margin counted as part of
 * them: on a tile with only a little room to spare, contents plus fifty pixels
 * came to more than the box could hold, so there was no slack left to move and
 * everything stayed exactly where it was. */
/* Any last child, not only a .ds-el one. wpautop leaves loose paragraphs in a
   column that never went through the element renderer and so carry no class of
   ours at all - and those have a margin under them like any other paragraph. */
.ds-col-in > :last-child{ margin-bottom:0 }

/* And the last thing in a plain column, which had no such rule.
 *
 * The element gap belongs between one element and the next. Left on the last
 * one it is a margin under the foot of the column: a section bottom-aligned in
 * a screen row ended a gap short of the column it sits in, so the words at the
 * foot of one column stopped 31px above the picture filling the next. Only a
 * column that wraps its content had this; a plain one did not. */
.ds-col > :last-child{ margin-bottom:0 }

.ds-col-vtop    > .ds-col-in{ display:flex; flex-direction:column; justify-content:flex-start }
.ds-col-vmiddle > .ds-col-in{ display:flex; flex-direction:column; justify-content:center }
.ds-col-vbottom > .ds-col-in{ display:flex; flex-direction:column; justify-content:flex-end }


/* One element at the foot of the column, everything else where it was.
 *
 * A top margin of auto in a flex column takes every pixel the column has spare,
 * so whatever carries it is pushed to the bottom and nothing above it moves. The
 * alternative was a margin big enough to reach - a figure only right at one
 * column height, wrong at every other, and wrong again the moment anything above
 * it changes length.
 *
 * The column is made a flex column only where something asks to sit at its foot.
 * A column laid out as a block behaves differently enough - margins collapse,
 * children size themselves differently - that switching every one of them would
 * be a change to every page on the site.
 *
 * The column also has to have a height for anything to be spare: on a bento that
 * is the tile, and on an ordinary row it is Columns sit left at Equal height.
 * Where a column is only as tall as its contents there is no room to push into,
 * and this correctly does nothing rather than doing something odd. */
.ds-col-hasbottom > .ds-col-in{ display:flex; flex-direction:column; min-height:100% }
.ds-col-in > .ds-el-bottom{ margin-top:auto }

/* And the column itself, filling its tile.
 *
 * min-height:100% is a share of the column, so the column has to have a height
 * of its own before anything can be spare. In a bento it is a grid item, and
 * with the row set to align its columns to the top that grid is
 * align-items:start - so the column is only as tall as its contents and stands
 * at the top of the tile, with the leftover height outside it where a margin of
 * auto inside can never reach. Measured on a home page bento: a 280px column in
 * a 318px tile, 38px under it that nothing could use, and Sits at the bottom of
 * the column correctly doing nothing.
 *
 * Stretched only where something has actually asked to sit at the foot, so a
 * row whose columns are meant to stand at the top is otherwise untouched. */
.ds-row-bento > .ds-row-in > .ds-col.ds-col-hasbottom{ align-self:stretch }

.ds-col-aleft   > .ds-col-in{ text-align:left }
.ds-col-acenter > .ds-col-in{ text-align:center }
.ds-col-aright  > .ds-col-in{ text-align:right }

/* ==============================================================
 * A notch cut out of the tile
 *
 * An element that sits against one corner of a bento tile, keeps the tile's own
 * padding as the space around it on the other sides, takes the same corner
 * radius as everything else, and has the tile's background removed from behind
 * it - so the words read as cut into the shape rather than laid on top of it.
 *
 * Three separate things have to line up, and only the first two are here:
 * hugging the corner, and the padding. The hole is punched by dsb-cut.js, which
 * measures the notch and writes a mask onto the tile's background layers.
 * ============================================================== */
.ds-el-cut{
  /* As far as the padding line, and no further.
   *
   * This was fit-content, so the notch was the size of the words in it and the
   * band of tile left beside it was whatever happened to be left over - eighty
   * pixels next to a short caption, twenty next to a long one, and no relation
   * to anything that had been set. A notch is defined by the tile's padding:
   * flush against the two edges it is butted to, and exactly one padding away
   * from the other two. Filling the well and then pulling two sides out by the
   * padding gives precisely that, at whatever the padding is later changed to.
   *
   * See .ds-col-cutting for the same thing vertically - a block only fills its
   * width on its own. */
  width: auto;
  /* No max-width. 100% is the width of the well, which is the tile less its
     padding on both sides - so it capped the notch there and cancelled out the
     negative margin that is supposed to carry it out to the butted edge. The
     result was a gap of two paddings on the free side instead of one. Nothing
     needs capping now that the width is not content driven: the notch stretches
     to its container and the negative margin extends it by exactly the padding
     it is undoing. */

  /* No background of its own. What shows through is whatever the tile is
     standing on, once the mask has taken the tile's own background away.
   *
   * The color only, never the shorthand. A heading set in a gradient paints
   * the words with a background image and sets the text itself transparent so
   * the image shows through the glyphs - and `background: transparent` throws
   * that image away, leaving transparent text with nothing behind it. The
   * notch came out as an empty hole with its words gone. */
  background-color: transparent;
  border-radius: var(--ds-card-radius, 12px);

  /* The tile's padding, kept as the room inside the notch. Read from the tile
     rather than typed again, so the two cannot drift apart. */
  padding:
    var(--ds-colpad-top, var(--ds-pad-base, 0px))
    var(--ds-colpad-right, var(--ds-pad-base, 0px))
    var(--ds-colpad-bottom, var(--ds-pad-base, 0px))
    var(--ds-colpad-left, var(--ds-pad-base, 0px));

  position: relative;
  z-index: 2;
}

/* The notch reaches the padding line downwards as well as across.
 *
 * A block fills the width it is given and takes only the height of its contents,
 * so a notch that was correct across the tile still left a band above or below it
 * as tall as whatever the words happened to be short by. The well becomes a
 * column and the notch is told to grow, which gives the same rule on both axes:
 * flush on the two butted edges, one padding on the other two.
 *
 * Only where there is a notch. Every other column is left exactly as it was. */
.ds-col-hascut > .ds-col-in{
  display: flex;
  flex-direction: column;
  min-height: 100%;
}
.ds-col-hascut > .ds-col-in > .ds-el-cut{
  flex: 1 1 auto;

  /* The spare height collects at one end, not between the lines.
   *
   * The notch is stretched to reach the padding line, and where its contents are
   * a wrapping flex box - a counter is exactly that, a figure and a caption on
   * two lines - the default is to share the extra height out between those
   * lines. So growing the notch pushed the figure down the box and left a gap
   * above it far larger than the padding, which is not a padding at all but
   * distributed slack. Packed to one end, the slack falls where nobody reads it
   * as a gap. */
  align-content: flex-start;
}

/* And which end is the column's own Vertical alignment.
 *
 * Stretching the notch is what stops the tile having a band of leftover color
 * round it, but it also means the column's alignment has nothing left to move -
 * the notch already fills the tile, so setting a tile to Bottom left its words
 * exactly where they were. The alignment is handed down to the contents of the
 * notch instead, which is what somebody setting it is actually asking for. */
.ds-col-hascut.ds-col-vtop > .ds-col-in > .ds-el-cut{ align-content: flex-start }
.ds-col-hascut.ds-col-vmiddle > .ds-col-in > .ds-el-cut{ align-content: center }
.ds-col-hascut.ds-col-vbottom > .ds-col-in > .ds-el-cut{ align-content: flex-end }
/* Anything else in the tile keeps its own height, so a heading above a notch is
   not stretched to share the space with it. */
.ds-col-hascut > .ds-col-in > :not(.ds-el-cut){ flex: 0 0 auto }

/* Hugging a corner is undoing the tile's padding on exactly two sides.
 *
 * A negative margin of the same figure the tile is padded by, so the notch
 * reaches the edge however that padding is later changed. The other two sides
 * keep the gap, which is what makes it read as cut in rather than as a block
 * sitting in the corner. */
.ds-cut-tl{ margin-top: calc(var(--ds-colpad-top, var(--ds-pad-base, 0px)) * -1);    margin-left:  calc(var(--ds-colpad-left, var(--ds-pad-base, 0px)) * -1);  margin-right: auto }
.ds-cut-bl{ margin-bottom: calc(var(--ds-colpad-bottom, var(--ds-pad-base, 0px)) * -1); margin-left:  calc(var(--ds-colpad-left, var(--ds-pad-base, 0px)) * -1);  margin-right: auto }

/* Pushed across, not merely widened.
 *
 * The notch is only as wide as its words, so it sits where the flow puts it -
 * the left. A negative right margin extends its box rightward and moves the
 * element not at all, which is why a notch marked top right appeared in the
 * top left. The auto margin on the other side is what actually carries it
 * over to the edge it is meant to hug. */
.ds-cut-tr{ margin-top: calc(var(--ds-colpad-top, var(--ds-pad-base, 0px)) * -1);    margin-right: calc(var(--ds-colpad-right, var(--ds-pad-base, 0px)) * -1); margin-left: auto }
.ds-cut-br{ margin-bottom: calc(var(--ds-colpad-bottom, var(--ds-pad-base, 0px)) * -1); margin-right: calc(var(--ds-colpad-right, var(--ds-pad-base, 0px)) * -1); margin-left: auto }

/* The mask, written by the script as an SVG of the tile with the notch taken
   out of it. Applied to every background layer and to the tile's own color,
   which is why the color is drawn as a layer rather than on the column.
 *
 * Nothing at all until the script has measured, so a tile whose script has not
 * run yet is a whole tile rather than a half-masked one. */
.ds-col-cutting > .ds-col-pic,
.ds-col-cutting > .ds-col-vid,
.ds-col-cutting > .ds-col-fill{
  -webkit-mask-image: var(--ds-cut-mask);
  mask-image: var(--ds-cut-mask);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* The tile's flat color and gradient, as a layer of their own.
 *
 * They used to be painted on the column itself, which cannot be masked without
 * masking everything in it as well. Drawn here instead, behind the content and
 * alongside the picture, so one mask takes the notch out of all of them. */
.ds-col-fill{
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  pointer-events: none;
}

/* The surface color as a maskable layer, the same color .ds-col-surface uses. */
.ds-col-fill-surface{ background-color: var(--ds-swiss-col-bg, var(--ds-card-bg, rgba(127,127,127,.08))) }

/* ==============================================================
 * Words turned on their side
 *
 * The angle is an inline variable rather than an inline transform, so a media
 * query can set it back to nought - a stylesheet cannot unpick a transform that
 * was written straight onto the tag.
 *
 * Standing back up matters more than it sounds. A line turned ninety degrees is
 * as tall as the words are long, and on a narrow screen that is taller than the
 * screen; the same line laid flat is wider than the column. Either way it
 * breaks the layout, so the author says where it gives up.
 * ============================================================== */
/* !important, and it has to be.
 *
 * The angle is written onto the tag, because it belongs to one element. An
 * inline declaration outranks any stylesheet rule whatever its media query
 * says, so the plain version of this did nothing at all - the class was on
 * the element, the query matched, and the words stayed on their side.
 *
 * An author stylesheet marked important does beat a normal inline
 * declaration, which is the one case where this is the tool rather than a
 * shortcut. */
@media (max-width: 1024px){
  .ds-rot-off-tablet{
    --ds-rot: 0deg !important;
    /* And the room it was taking. An element left as inline-block after the
       turn is gone sits in a box the size of its words, which is right - but it
       keeps whatever width the turn had reserved unless this is said. */
    display: inline;
  }
}

@media (max-width: 640px){
  .ds-rot-off-mobile{
    --ds-rot: 0deg !important;
    display: inline;
  }
}

/* Elements taken off one size of screen.

   Written here beside the rotation's own breakpoints on purpose: these are the
   same two widths, and two places deciding what "tablet" means is how a builder
   ends up hiding something at 1024 and straightening it at 900.

   display:none rather than visibility:hidden. The point is that the element is
   not there at this width - hidden but occupying its space leaves a gap nobody
   can explain, and a reader who finds one has no way of knowing it is deliberate.

   !important because the element may carry its own display from its own styles -
   a flex row, an inline-block heading - and a rule about whether it exists at
   this width has to beat a rule about how it lays out when it does. */
@media (max-width: 1024px){
  .ds-hide-tablet{ display: none !important }
}

@media (max-width: 640px){
  .ds-hide-mobile{ display: none !important }
}

/* And the other way round: on the screens the first two leave it on.

   Above the tablet breakpoint rather than below it, so an element hidden on
   desktop is back the moment the tablet rule would have applied - the two
   between them cover every width once, with no gap and no overlap. */
@media (min-width: 1025px){
  .ds-hide-desktop{ display: none !important }
}

/* ==============================================================
 * Reaching across, once there is nothing to reach across into
 *
 * A heading laid over the picture in the next column does it with a negative
 * side margin. That is exactly right while the columns sit side by side, and
 * meaningless the moment they stack - there is no next column, so the margin
 * stops being an overlap and becomes 118px of page nobody can see.
 *
 * So the reach is released where the stacking happens, and only sideways: a
 * negative top margin is how a picture is pulled up out of the top of a tile,
 * which still means exactly what it meant.
 *
 * Direct children of a column only. An element that has gone looking for a
 * negative margin somewhere deeper has its own reasons.
 * ============================================================== */
@media (max-width: 1024px){
  /* Clipped at the row rather than by unpicking the margin.
   *
   * The margin is written onto the element, so a stylesheet cannot lower it
   * without !important - and doing that would flatten deliberate positive
   * margins along with the negative ones. Clipping leaves every margin
   * exactly as the author set it and simply stops the reach painting where
   * there is no longer a column to reach into.
   *
   * clip rather than hidden: hidden makes a scroll container, which breaks
   * position:sticky in anything above it and gives the row a scrollbar of
   * its own. clip does neither. */
  .ds-row > .ds-row-in{ overflow-x: clip }
}

/* ==============================================================
 * A mark in the corner, a tile that answers the pointer, a tile that links
 * ============================================================== */

/* The mark. Sits in the padding of its corner, so it lines up with whatever
   else is inset by the tile's own padding rather than floating at a distance
   nobody chose. */
.ds-col-icon{
  position: absolute;
  z-index: 1;
  width: var(--ds-colicon, 28px);
  height: var(--ds-colicon, 28px);
  display: grid;
  place-items: center;
  pointer-events: none;
  color: currentColor;
  transition: transform .35s cubic-bezier(.22,.61,.36,1), opacity .35s ease;
}
.ds-col-icon svg{ width: 100%; height: 100%; display: block }

.ds-colicon-tl{ top: var(--ds-colpad-top, 16px);    left:  var(--ds-colpad-left, 16px) }
.ds-colicon-tr{ top: var(--ds-colpad-top, 16px);    right: var(--ds-colpad-right, 16px) }
.ds-colicon-bl{ bottom: var(--ds-colpad-bottom, 16px); left:  var(--ds-colpad-left, 16px) }
.ds-colicon-br{ bottom: var(--ds-colpad-bottom, 16px); right: var(--ds-colpad-right, 16px) }

/* Cut through the tile rather than drawn on it. The mark becomes a hole in the
   background exactly as the notch does, so what shows through it is whatever
   the tile is standing on. */
/* Cut through the tile rather than drawn on it.
 *
 * destination-out was the wrong tool: it needs the mark and the background
 * to be in one stacking context with nothing else in it, which a tile full of
 * words never is - so it composited against the page and did nothing at all.
 *
 * The mark is used as a mask on the layers instead, the same way the notch
 * is. What shows through is whatever the tile is standing on. */
.ds-colicon-cut{ visibility: hidden }

.ds-col-iconcut > .ds-col-pic,
.ds-col-iconcut > .ds-col-vid,
.ds-col-iconcut > .ds-col-fill{
  -webkit-mask-image: var(--ds-iconcut-mask);
  mask-image: var(--ds-iconcut-mask);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  /* Read by brightness, not by opacity.
   *
   * The mask is a white sheet with the mark drawn on it in black, and both are
   * fully opaque - so read the usual way, as alpha, every part of it said
   * "show this" and the mark cut nothing out. By luminance, white shows and
   * black hides, which is what the drawing has always meant. */
  -webkit-mask-source-type: luminance;
  mask-mode: luminance;
}

/* The whole tile as a link. Over the background, under the content - so a
   button inside the tile is still its own button. */
/* A div around the anchor, because an anchor on its own is an inline element
   and wpautop reads a loose inline run as a paragraph somebody forgot to
   open - which left a stray empty paragraph in every linked tile. The div
   holds the place and the anchor fills it. */
.ds-col-link{
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
}
.ds-col-link a{
  position: absolute;
  inset: 0;
  display: block;
  border-radius: inherit;
}
.ds-col-linked{ cursor: pointer }
.ds-col-linked > .ds-col-in{ position: relative; z-index: 2 }

/* Only what can be clicked in its own right takes the pointer back. Plain words
   let the click through to the tile's link, which is what makes the whole tile
   feel like one thing. */
.ds-col-linked > .ds-col-in a,
.ds-col-linked > .ds-col-in button,
.ds-col-linked > .ds-col-in input,
.ds-col-linked > .ds-col-in select,
.ds-col-linked > .ds-col-in textarea{ position: relative; z-index: 3 }

.ds-col-link a:focus-visible{
  outline: 2px solid var(--ds-accent, currentColor);
  outline-offset: 3px;
}

/* ---- What it does when pointed at ----
 *
 * The background moves, never the tile. Moving the tile itself shifts the words
 * with it and pushes against its neighbors in the grid; moving what is behind
 * them leaves the layout exactly where it was.
 *
 * Slightly larger than the frame to begin with, so there is something to slide
 * into view rather than an edge arriving. */
/* The mark moves. The background does not.
 *
 * Every one of these used to slide or scale the picture behind the tile, on the
 * reasoning that moving the background moves nothing in the layout. That was the
 * wrong thing to move: the setting is about the corner mark, a photograph
 * lurching four percent sideways under the words is not what anybody asked a
 * mark to do, and it fought the mask on a tile with a cut. So the picture is
 * left alone and the mark is what answers the pointer.
 *
 * The transition belongs on the mark for the same reason. Without one, the
 * transform below applied on the frame the pointer arrived and the mark simply
 * appeared four pixels along - which read as the mark not moving at all. */
.ds-col-icon{
  transition: transform .4s cubic-bezier(.22,.61,.36,1);
  will-change: transform;
}

/* Rotate turns the mark rather than the picture. A photograph on its side is a
   mistake; an arrow turning to point somewhere is the thing people mean. */
.ds-colhov-rotate:hover > .ds-col-icon{ transform: rotate(45deg) }

/* And the mark leans whichever way the setting is named after. */
.ds-colhov-slide-left:hover  > .ds-col-icon{ transform: translateX(-25%) }
.ds-colhov-slide-right:hover > .ds-col-icon{ transform: translateX(25%) }
.ds-colhov-slide-up:hover    > .ds-col-icon{ transform: translateY(-25%) }
.ds-colhov-slide-down:hover  > .ds-col-icon{ transform: translateY(25%) }
.ds-colhov-scale:hover       > .ds-col-icon{ transform: scale(1.25) }

/* Asked for less movement means none of it. */
@media (prefers-reduced-motion: reduce){
  .ds-col-hover > .ds-col-pic,
  .ds-col-hover > .ds-col-vid,
  .ds-col-hover > .ds-col-fill,
  .ds-col-icon{ transition: none }
  .ds-colhov-slide-left:hover > .ds-col-pic,
  .ds-colhov-slide-right:hover > .ds-col-pic,
  .ds-colhov-slide-up:hover > .ds-col-pic,
  .ds-colhov-slide-down:hover > .ds-col-pic,
  .ds-colhov-scale:hover > .ds-col-pic,
  .ds-colhov-rotate:hover > .ds-col-icon{ transform: none }
}

/* Drift: the picture leans a few pixels toward the pointer.
 *
 * Applied as a translate the script only feeds numbers into, so it composes
 * with the settling scale above rather than the two overwriting each other. */
/* A picture leans by moving inside its layer, so the layer - and the mask on it -
   never moves. See render_column: the position is written as two percentages with
   the drift added, which is why nothing here touches the pic. */
.ds-col-drift > .ds-col-pic{
  transition: background-position .35s cubic-bezier(.22,.61,.36,1);
}

/* A video has no background to move, so it is the element that leans, and it is
   drawn a little larger so leaning never shows an edge. A flat color has
   nothing to lean at all. */
.ds-col-drift > .ds-col-vid{
  translate: var(--ds-driftx, 0px) var(--ds-drifty, 0px);
  transition: translate .35s cubic-bezier(.22,.61,.36,1);
  scale: 1.06;
}

/* While the pointer is actually on it, the lean should keep up rather than
   trail by a third of a second. */
.ds-col-drift:hover > .ds-col-pic{ transition: background-position .12s linear }
.ds-col-drift:hover > .ds-col-vid{ transition: translate .12s linear }

/* A video still leans as an element, so a mask on it is put back by the same
   distance to hold it against the tile. A picture needs none of this - it is the
   background inside a layer that never moved. */
.ds-col-drift > .ds-col-vid{
  -webkit-mask-position: calc(-1 * var(--ds-driftx, 0px)) calc(-1 * var(--ds-drifty, 0px));
  mask-position: calc(-1 * var(--ds-driftx, 0px)) calc(-1 * var(--ds-drifty, 0px));
}

@media (prefers-reduced-motion: reduce){
  .ds-col-drift > .ds-col-vid{ translate: none }
  /* And the picture sits at the position it was given, with no drift added. */
  .ds-col-drift > .ds-col-pic{ --ds-driftx: 0px; --ds-drifty: 0px }
}

/* ==============================================================
 * Badge
 *
 * A word in a pill, drawn from the tokens Theme Settings already sets under
 * Elements, Badge - the same ones the store uses for its product badges. Nothing
 * about the look is decided here, so a site that restyles its badges restyles
 * these with them.
 * ============================================================== */
.ds-badge{
  display: inline-flex;
  align-items: center;
  /* No element gap while it is flowing with the words.
   *
   * It carries ds-el, which is what gives an element its margin below and lets
   * an author's own margin apply - but that gap is fifty pixels by default, and
   * fifty pixels under a pill sitting in the middle of a sentence is not a gap,
   * it is a hole. A badge on a line of its own keeps it; see .ds-badge-block. */
  margin-bottom: 0;
  /* Inline, so several sit on one line and flow with the words around them.
     That is the whole reason this exists rather than a Button. */
  vertical-align: middle;
  font-family: var(--ds-badge-font, inherit);
  font-size: var(--ds-badge-size, max(calc(12px * var(--ds-lo, .5333)), 0.625vw));
  line-height: 1.3;
  letter-spacing: .04em;
  white-space: nowrap;
  padding: var(--ds-badge-pad-y, 4px) var(--ds-badge-pad-x, 10px);
  border-radius: var(--ds-badge-radius, 99px);
  border: 1px solid transparent;
  text-decoration: none;
}

.ds-badge-solid{
  background: var(--ds-badge-bg, var(--ds-accent, #bdfe02));
  color: var(--ds-badge-text, #000);
  border-color: var(--ds-badge-border, transparent);
}

/* The outline variant the theme already has tokens for. */
.ds-badge-outline{
  background: var(--ds-badge-out-bg, transparent);
  color: var(--ds-badge-out-text, currentColor);
  border-color: var(--ds-badge-out-border, currentColor);
}

/* A badge given a line of its own.
 *
 * Still only as wide as its word. width:100% with justify-content would have
 * stretched the pill across the column, which is a bar and not a badge. It takes
 * a line by being a block that fits its contents, and it is placed on that line
 * with automatic margins. */
.ds-badge-block{ display: flex; width: fit-content; margin-bottom: var(--ds-el-gap, 50px) }
.ds-badge-block.ds-align-left  { margin-inline: 0 auto }
.ds-badge-block.ds-align-center{ margin-inline: auto }
.ds-badge-block.ds-align-right { margin-inline: auto 0 }

/* A badge that goes somewhere still says so under the pointer. */
a.ds-badge:hover, a.ds-badge:focus-visible{ filter: brightness(1.08) }

/* ==============================================================
 * Before / After
 *
 * Two pictures of the same thing with a handle saying how much of the top one
 * to show. The one underneath is the whole picture at its natural size, so the
 * element is as tall as the photograph is; the one on top is laid over it at
 * exactly the same size and clipped rather than resized, because two pictures
 * scaled differently put the same feature in two places and compare nothing.
 *
 * One figure drives it. --ds-ba-at is the split, and the clip and the handle
 * both read it, so they cannot come apart. Set on the element by PHP and moved
 * by the script; with no script at all it still draws at the author's figure.
 * ============================================================== */
.ds-ba{
  position:relative;
  overflow:hidden;
  touch-action:none;
  --ds-ba-at:50%;
}

.ds-ba-hasratio{ aspect-ratio:var(--ds-ba-ratio) }

.ds-ba img{ display:block; width:100%; height:auto }
.ds-ba-hasratio img{ height:100%; object-fit:cover }

/* The one underneath sets the size. The one on top is taken out of the flow and
   pinned to it, so both are the same box whatever the picture's shape. */
.ds-ba-before{ position:absolute; inset:0 }
.ds-ba-before img{ width:100%; height:100%; object-fit:cover }

/* Clipped, not scaled. inset() keeps the picture where it is and hides the part
   past the split, which is what makes the two line up. */
.ds-ba-x .ds-ba-before{ clip-path:inset(0 calc(100% - var(--ds-ba-at)) 0 0) }
.ds-ba-y .ds-ba-before{ clip-path:inset(0 0 calc(100% - var(--ds-ba-at)) 0) }

.ds-ba-r-soft, .ds-ba-r-soft img{ border-radius:var(--ds-radius-soft,10px) }
.ds-ba-r-round, .ds-ba-r-round img{ border-radius:var(--ds-radius-round,24px) }

/* The handle. A line across the picture with a grip on it, drawn rather than
   given a background image so it takes the site's accent and needs no asset. */
.ds-ba-handle{
  position:absolute;
  background:var(--ds-accent,#fff);
  cursor:grab;
}
.ds-ba-handle:active{ cursor:grabbing }

.ds-ba-x .ds-ba-handle{ top:0; bottom:0; left:var(--ds-ba-at); width:2px; transform:translateX(-1px) }
.ds-ba-y .ds-ba-handle{ left:0; right:0; top:var(--ds-ba-at); height:2px; transform:translateY(-1px) }

/* The grip, centered on the line. Its own box so the line stays thin. */
.ds-ba-handle::after{
  content:'';
  position:absolute;
  top:50%; left:50%;
  width:38px; height:38px;
  margin:-19px 0 0 -19px;
  border-radius:50%;
  background:var(--ds-accent,#fff);
  box-shadow:0 2px 12px rgba(0,0,0,.35);
}

.ds-ba-handle:focus-visible{ outline:2px solid var(--ds-accent,#fff); outline-offset:3px }

/* Words over each half, if there are any. Held to the corners furthest from the
   handle so neither sits under it at the middle. */
.ds-ba-tag{
  position:absolute;
  padding:.35em .7em;
  font-size:var(--ds-p-size,1rem);
  line-height:1.2;
  background:rgba(0,0,0,.55);
  color:#fff;
  border-radius:var(--ds-radius-soft,10px);
  pointer-events:none;
}
.ds-ba-x .ds-ba-tag-before{ top:12px; left:12px }
.ds-ba-x .ds-ba-tag-after { top:12px; right:12px }
.ds-ba-y .ds-ba-tag-before{ top:12px; left:12px }
.ds-ba-y .ds-ba-tag-after { bottom:12px; left:12px }

@media (prefers-reduced-motion: reduce){
  .ds-ba-handle{ transition:none }
}

/* ---------------------------------------------------------------------------
 * Popups
 *
 * A real <dialog>, so the browser handles Escape, focus and the backdrop. What
 * is left here is the shape of the box.
 *
 * A popup wears the theme's modal styling - every color below is the one
 * Theme Settings writes under Lightbox, the same set .ds-modal-panel reads.
 * A site that has dressed its modals has already answered what a box over the
 * page looks like there, and asking again under a second set of names would
 * let one site have two different modals.
 *
 * This block used to say background:#fff with color:inherit - a surface pinned
 * to white against whatever color the page happened to be using. On a dark
 * theme the inherited ink is near white, so the body copy and the close button
 * came out white on white: not faint, gone. A surface and its text are one
 * decision and have to come from one place.
 * ------------------------------------------------------------------------ */
.ds-popup{
  width: min(92vw, var(--ds-popup-w, 640px));
  max-width: 92vw;
  max-height: 88vh;
  padding: 0;
  border: var(--ds-modal-bw, 1px) solid
    var(--ds-modal-edge, var(--ds-line, rgba(255,255,255,.16)));
  border-radius: var(--ds-field-radius,4px);
  background: var(--ds-modal-bg, var(--ds-bg,#0b0b0c));
  color: var(--ds-modal-ink, var(--ds-text,#f5f5f5));
  overflow: auto;
}

/* The named sizes, which are the modal panel's own figures under the modal
   panel's own names - so a popup and a lightbox modal both set to Large come
   out the same width on the same site.

   92vw stays in front of every one of them: these are desktop measurements and
   a 960px box on a phone is a box wider than the screen. */
.ds-popup-small { width: min(420px, 92vw) }
.ds-popup-medium{ width: min(680px, 92vw) }
.ds-popup-large { width: min(960px, 92vw) }
.ds-popup-full  {
  width: 100%; max-width: 100%;
  max-height: 100svh;
  border: 0; border-radius: 0;
}

/* The scrim is the modal's too, so a popup darkens the page by as much as a
   lightbox does rather than by its own figure. */
.ds-popup::backdrop{ background: var(--ds-modal-scrim, rgba(0,0,0,.6)) }

/* The modal panel's own inset, so the two are spaced alike. */
.ds-popup-body{ padding: var(--ds-modal-pad, max(24px, min(4vw, 48px), 2.5vw)) }

/* Headings answer to the modal's heading color, exactly as they do inside a
   modal panel - one control for a pale heading over quieter body copy. */
.ds-popup :is(h1,h2,h3,h4,h5,h6){ color: var(--ds-modal-head, inherit) }

/* Over the corner of the content rather than above it, so it costs no height
   and is in the same place whatever the layout inside is.

   Dressed from the modal's close button, not the popup's own idea of one. */
.ds-popup-x{
  position: absolute; top: 8px; right: 10px; z-index: 2;
  width: 32px; height: 32px; padding: 0;
  display: grid; place-items: center;
  border: 1px solid var(--ds-modalx-edge, var(--ds-line, rgba(255,255,255,.16)));
  border-radius: 50%;
  background: var(--ds-modalx-bg, rgba(255,255,255,.04));
  color: var(--ds-modalx-ink, var(--ds-text,#fff));
  font-size: max(calc(24px * var(--ds-lo, .5333)), 1.25vw); line-height: 1; cursor: pointer;
}
.ds-popup-x:hover,
.ds-popup-x:focus-visible{
  color: var(--ds-modalx-ink-hover, #fff);
  background: var(--ds-modalx-bg, rgba(255,255,255,.12));
  border-color: var(--ds-modalx-edge, rgba(255,255,255,.4));
  filter: brightness(1.25);
}

/* A row inside a popup has no page gutter to fill - it is already inside a box
   with its own padding, and a full width row would sit against the edges. */
.ds-popup .ds-row{ width: 100%; margin-left: 0; margin-right: 0 }

/* And neither has the grid inside it.
 *
 * .ds-row-in carries the page's content gutter, which is the right figure on a
 * page and a second inset inside a box that already has one. It measured 50px a
 * side on top of the body's own padding, so a 640px popup was laying its fields
 * out in 485px and the content sat well short of the panel it is in.
 *
 * The max-width goes with it for the same reason: that is the page's content
 * measure, and a popup is already as wide as it was told to be. */
.ds-popup .ds-row-in{
  padding-left: 0;
  padding-right: 0;
  max-width: none;
}

@media (prefers-reduced-motion: no-preference){
  .ds-popup[open]{ animation: ds-popup-in .18s ease both }
  @keyframes ds-popup-in{ from{ opacity:0; transform:translateY(6px) } to{ opacity:1; transform:none } }
}

/* ------------------------------------------------------------------ tables
 *
 * The builder has no table element, so a table reaches a page inside an HTML
 * block carrying whatever the browser gives it - cramped rows, a hairline
 * nobody chose and no alignment on the figures. These are the house rules for
 * one, named so any block can ask for them rather than each one arriving with
 * its own styles pasted in above it.
 *
 * Everything is taken from the theme's own variables, so a table follows the
 * site's colours and type rather than introducing a second palette. */
.ds-table{ width:100%; overflow-x:auto; -webkit-overflow-scrolling:touch }

.ds-table table{
  width:100%;
  /* Equal columns. Left to itself the browser sizes each column to its widest
     cell, so a table of prices ends up with one fat column of names and four
     thin ones of numbers. */
  table-layout:fixed;
  border-collapse:collapse;
  /* Fluid, with a floor and no ceiling - the house calculation.
   *
   * This was clamp(.95rem, 1.1vw, 1.05rem), and the ceiling is what stopped
   * it: 16.8px from about 1530 upwards, so on a 4K screen the whole table
   * stayed the size it is on a laptop while the words around it doubled. The
   * figure is what it draws at 1920 and the same share of the window above
   * that, which is the rule every other size in the theme follows. */
  font-size:max(1.05rem, .875vw);
}

.ds-table th,
.ds-table td{
  /* The air in a cell travels with the words in it. Fixed padding around
     growing type closes the gap between columns as the screen gets bigger. */
  padding:max(.875rem, .7292vw) max(1rem, .8333vw);
  text-align:left;
  border-bottom:1px solid var(--ds-line, rgba(0,0,0,.12));
  vertical-align:baseline;
}

.ds-table thead th{
  font-weight:600;
  border-bottom-width:2px;
  white-space:nowrap;
}

/* Money lines up on the right and on its digits, or a column of prices cannot
   be read down. tabular-nums stops a 1 being narrower than a 9.
 *
 * Every cell but the first, rather than "a td following a td". A body row opens
 * with a th - the row's own label - so its second cell is a td following a th,
 * which that selector never reached: the figures in that column stayed left
 * while the heading above them sat right, and the column read as misaligned. */
.ds-table tr > *:not(:first-child){
  text-align:right;
  font-variant-numeric:tabular-nums;
  white-space:nowrap;
}

/* The line that adds it all up. */
.ds-table tfoot th,
.ds-table tfoot td{
  font-weight:700;
  border-top:2px solid currentColor;
  border-bottom:0;
  font-size:1.15em;
}

/* The column being argued for. */
/* The column being argued for.
 *
 * Weight is carried by the heading and by the totals line, which are the two
 * places it means something. A body cell repeating the same word down every
 * row does not need it, and bolding all of them made the column read as the
 * loudest thing on the table rather than the answer to it. The totals line
 * keeps its own weight from the tfoot rule above. */
.ds-table thead .is-ours{ font-weight:600 }

/* The tick beside "Included".
 *
 * On the text baseline rather than centred on the line box, so it sits with
 * the word instead of floating above it, and given a little air on its left.
 * It inherits the cell's color, so it follows the row into black on hover
 * with everything else. */
.ds-table .ds-table-tick{ display:inline-block; margin-right:max(.375rem, .3125vw); vertical-align:-2px }

/* The line under a row's label - what it is being compared against.
 *
 * Its own line and quieter than the label, because it is the evidence for the
 * figure beside it rather than part of the thing being named. Normal weight
 * inside a th, which is bold by default and would otherwise give a competitor's
 * name the same emphasis as the category. */
/* No side padding on a phone.
 *
 * Four columns in a 390px window leaves under a hundred pixels a column, and
 * sixteen pixels of padding each side takes a third of that before a word is
 * drawn. The rule between rows still separates them and the first column still
 * starts at the edge of the text above it, which is where a reader expects a
 * table to start. Vertical padding is untouched - the rows still need air. */
@media (max-width: 560px){
  .ds-table th,
  .ds-table td{ padding-left:0; padding-right:0 }

  /* Except between columns, or the price runs into the name beside it. */
  .ds-table tr > *:not(:first-child){ padding-left:10px }
}

.ds-table .ds-table-sub{
  display:block;
  font-weight:400;
  font-size:.85em;
  opacity:.6;
  margin-top:max(.125rem, .1042vw);
  white-space:normal;
}

/* A column's own vertical alignment, inside a screen row.
 *
 * The row's alignment is set on the column so a picture can stretch; a column
 * that names its own has to beat that, and its inner box has to be the full
 * height for the alignment to have anywhere to push against. Without this a
 * column set to bottom sat wherever the row put it - which is most of what was
 * wrong with the deck rows: text that belongs at the foot of a column was
 * floating in the middle of it. */
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col[class*="ds-col-v"] > .ds-col-in{
  flex: 1 1 auto;
  min-height: 0;
}

.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col.ds-col-vtop   { justify-content: flex-start }
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col.ds-col-vmiddle{ justify-content: center }
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col.ds-col-vbottom{ justify-content: flex-end }

/* While the columns are still side by side.
 *
 * Once they stack, a Swiss row is one column wide and its voids are folded
 * away, so holding its columns to a share of one screen is not keeping a
 * layout - it stacks pictures on top of each other, which is what these rules
 * did to the phone the first time they were written without a query.
 *
 * 782, the width the columns actually collapse at - not the tablet
 * breakpoint. Written at 1025 this gave up 243px too early, so on a tablet a
 * Swiss row's columns fell to their own content height and anything set to
 * sit at the foot of one had nothing to sit against. */


/* A screen row is a screen at least, and as much more as it needs.
 *
 * This was height and max-height together with overflow:hidden, so the row was
 * exactly a screen and anything taller was cut off at the fold - a picture
 * missing its bottom third, a paragraph ending mid-line. Clipping is never the
 * right answer to content that does not fit: it destroys the content and says
 * nothing to whoever built the page.
 *
 * The floor stays, so a row asked to be a screen still fills one when its
 * content is short and the pictures still divide the height between them. The
 * ceiling is gone, so a row whose content needs more simply becomes taller and
 * the page scrolls, which is what a page does. */
.ds-row-screen.ds-cols-stretch{
  min-height: var(--ds-screen, 100svh);
  overflow: visible;

  /* Air above and below, because a screen row is the one kind that needs it.
   *
   * The gutter is what holds content off the edge of the window, and a screen
   * row had it on two sides only - so a picture filling its column ran hard
   * into the top and bottom of the glass while its neighbours kept a margin.
   *
   * The site's own row padding first, and the gutter only where that has never
   * been set. This was the gutter outright, which made one number the side
   * space of the page AND the top and bottom of every full screen row: pulling
   * the page in from the edge dropped every hero's content, and there was no
   * way to say one without the other. Now the Rows card owns this and the
   * gutter is the fallback, so a site that never opens it is unchanged. */
  padding-block: var(--ds-row-pt, var(--ds-gutter, 24px)) var(--ds-row-pb, var(--ds-gutter, 24px));
}

/* A tile that stands more than one row fills them.
 *
 * The stretch reached one row tiles and stopped: a tile spanning two sat at
 * its own height in the middle of the pair with white above and below it,
 * lining up with nothing. */
/* A tile that is a picture is the picture, edge to edge.
 *
 * Only a column that names something extra gets the .ds-col-in wrapper, and
 * the wrapper carries the column padding - so in a bento the one tile that
 * spans two rows had a wrapper and 21px of padding while the four beside it
 * had neither. That is the uneven gap around the tall tile. */
.ds-row-screen.ds-cols-stretch.ds-row-bento .ds-row-in > .ds-col > .ds-col-in:has(> .ds-figure:only-child){
  padding: 0;
}

/* And no gap under it either. The gap belongs between one element and the next
   inside a column; a tile has no next, and the margin only shortened it inside
   the cell it is meant to be. The grid's own gap is what separates tiles. */
.ds-row-screen.ds-cols-stretch.ds-row-bento .ds-row-in > .ds-col > .ds-figure,
.ds-row-screen.ds-cols-stretch.ds-row-bento .ds-row-in > .ds-col > .ds-col-in > .ds-figure{
  margin-bottom: 0;
}

.ds-row-screen.ds-cols-stretch.ds-row-bento .ds-row-in > .ds-col > .ds-figure,
.ds-row-screen.ds-cols-stretch.ds-row-bento .ds-row-in > .ds-col > .ds-col-in > .ds-figure{
  flex: 1 1 auto;
  /* Both, and both explicit. Height alone left the tile at the picture's own
     width - 886px of image in a 928px tile - so the tall tile stood inset on
     every side while the small ones sat flush, and the gaps around it read as
     wider than the gaps between the rest. */
  width: 100%;
  height: 100%;
  min-height: 0;
}


.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col > .ds-figure,
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col > .ds-col-in > .ds-figure{
  flex: 1 1 0;
  min-height: 0;
}

.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col > .ds-figure img,
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col > .ds-col-in > .ds-figure img{
  width: 100%;
  height: 100%;
  object-fit: var(--ds-img-fit, cover);
}

/* The wrapper a column gets when it names its own alignment is not a box in the
 * design - it is where the alignment is applied. Every rule that makes a screen
 * row work was written for a column whose content is its own children, and the
 * wrapper put a box between them, so a column that named an alignment lost the
 * lot: pictures stopped taking the room the words left, stood at their own
 * height, and left the rest of the column empty. That is the void down the
 * middle of every row in the deck comparison.
 *
 * It carries the column's own layout so the rules below reach through it. */
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col > .ds-col-in{
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col.ds-col-vtop    > .ds-col-in{ justify-content: flex-start }
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col.ds-col-vmiddle > .ds-col-in{ justify-content: center }
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col.ds-col-vbottom > .ds-col-in{ justify-content: flex-end }

/* A column holding something that stretches has already decided where its
   content sits, so the alignment has nothing left to push against. */
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col > .ds-col-in:has(> .ds-figure),
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col > .ds-col-in:has(> .ds-inrow){
  justify-content: flex-start;
}

/* A section of a column is as tall as the share it was given, and what it holds
   sits where the section says - which is what makes a head at the top and a
   note at the foot one column rather than two guesses at a spacer height. */
/* Stretched, so the alignment below has the section's full height to push
   against. Left at end, the column shrinks to its content and there is nothing
   between the top of the section and the words for the alignment to move. */
.ds-row-screen.ds-cols-stretch .ds-col .ds-inrow{ min-height: 0; align-items: stretch }

.ds-row-screen.ds-cols-stretch .ds-col .ds-inrow > .ds-inrow-col{
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.ds-row-screen.ds-cols-stretch .ds-col .ds-inrow-top    > .ds-inrow-col{ justify-content: flex-start }
.ds-row-screen.ds-cols-stretch .ds-col .ds-inrow-middle > .ds-inrow-col{ justify-content: center }
.ds-row-screen.ds-cols-stretch .ds-col .ds-inrow-bottom > .ds-inrow-col{ justify-content: flex-end }

/* And a picture inside a section takes what the words in it leave, the same as
   one sitting straight in a column. */
.ds-row-screen.ds-cols-stretch .ds-col .ds-inrow-col > .ds-figure{
  flex: 1 1 auto;
  min-height: 0;
}

/* Words keep their own height and never squash. */
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col > .ds-heading,
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col > .ds-text,
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col > .ds-list,
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col > .ds-kicker,
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col > .ds-col-in > .ds-heading,
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col > .ds-col-in > .ds-text,
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col > .ds-col-in > .ds-list,
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col > .ds-col-in > .ds-kicker{
  flex: 0 0 auto;
}

/* A picture never leaves its place.
 *
 * In a screen row the layout is the thing being protected, not the photograph.
 * A picture takes the box it was given, fills it, and anything that does not
 * fit is clipped - because the alternative is a picture that pushes a column
 * out of shape and takes the row off the bottom of the window with it.
 *
 * Cover rather than contain: a letterboxed picture in a grid leaves a band of
 * page showing through and reads as a gap nobody put there. */
.ds-row-screen.ds-cols-stretch .ds-figure,
.ds-row-screen.ds-cols-stretch .ds-gal-tile{
  overflow: var(--ds-img-clip, hidden);
  border-radius: var(--ds-img-radius, 0);
}

/* The corner is the picture's, not the box's.
 *
 * A figure fills its column; a picture given its own size does not fill the
 * figure. Rounding only the figure put the radius on a box the eye cannot see
 * and left the picture inside it square on every corner. */
.ds-row-screen.ds-cols-stretch .ds-figure > img,
.ds-row-screen.ds-cols-stretch .ds-gal-tile > img{
  border-radius: var(--ds-img-radius, 0);
}

.ds-row-screen.ds-cols-stretch .ds-figure img,
.ds-row-screen.ds-cols-stretch .ds-gal-tile img{
  width: 100%;
  height: 100%;
  object-fit: var(--ds-img-fit, cover);
  object-position: var(--ds-img-pos, center);
  /* Nothing an intrinsic size can do to widen the box it sits in. */
  max-width: 100%;
  min-width: 0;
  min-height: 0;
}

/* And the boxes that hold them do not cut what is in them.
 *
 * This was overflow:hidden, to keep a stretched column inside the screen. What
 * it actually did was cut the content: a full screen row is min-height:100svh,
 * so it grows when it needs to, but a stretched column clipped instead - and a
 * slide laid out in a 900 tall window lost four lines of a paragraph in an 835
 * tall one, silently, with nothing on the page to say a word was missing.
 *
 * Text never clips. A column that has more in it than the screen makes the row
 * taller, which is the one behaviour that cannot lose anything. A tile that is
 * genuinely a frame around a photograph asks for .ds-col-clip by name. */
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col{ overflow: visible }

/* A picture takes what is left, but never nothing.
 *
 * flex 1 1 0 on its own let a figure in a column of words collapse to no
 * height at all - five of the deck rows came out as text on white with the
 * pictures gone. It shares what the words leave and holds a floor, and the row
 * is capped either way so nothing runs off the bottom. */
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col > .ds-figure,
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col > .ds-col-in > .ds-figure{
  flex: 1 1 auto;
  /* The least and the most of its column a picture may take, from General,
     Layout, Images. 22 and 100 are what the theme used before the card, so an
     untouched site keeps them. */
  min-height: var(--ds-img-min, 22%);
  max-height: var(--ds-img-max, 100%);
}

/* A column that is nothing but pictures gives them the whole of it. */
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col:not(:has(.ds-heading, .ds-text, .ds-list)) > .ds-figure,
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col:not(:has(.ds-heading, .ds-text, .ds-list)) > .ds-col-in > .ds-figure{
  min-height: 0;
}

/* And a column split into sections keeps no floor at all.
 *
 * The floor stops a picture collapsing where it shares a column with loose
 * words, which cannot shrink. Sections can: each one already takes a share of
 * the column, so a floor on top of them is a height nothing is obliged to
 * honour - the three together came to more than the column and the picture
 * printed over the section under it. */
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col:has(> .ds-inrow) > .ds-figure,
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col > .ds-col-in:has(> .ds-inrow) > .ds-figure{
  flex: 1 1 0;
  min-height: 0;
}

/* Where a column holds both, the sections keep the height their words need and
 * the picture takes everything they leave.
 *
 * Both growing meant they shared the column three ways, and a picture between
 * two blocks of type came out a sliver with white above and below it. A
 * section that is the only thing in its column still spreads, because there is
 * nothing else asking for the room. */
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col:has(> .ds-figure) > .ds-inrow,
.ds-row-screen.ds-cols-stretch .ds-row-in > .ds-col > .ds-col-in:has(> .ds-figure) > .ds-inrow{
  flex: 0 0 auto;
}

/* Where the columns are stacked one under another, holding the row to a single
 * screen is no longer keeping a layout - it is dividing everything the row
 * holds by however many pieces there are. Nine tiles came out as nine bands a
 * few pixels deep.
 *
 * A screen is the floor rather than the ceiling here, which is what the bento
 * rules above already do with their tile heights for the same reason.
 *
 * 781, not the tablet breakpoint. The columns collapse at 781 and not before,
 * so between there and 1024 they are still side by side and the row can hold a
 * screen exactly as it does on a desktop. Written at 1024 this let go 243px
 * too early: on a tablet every column fell to its own content height, and
 * anything set to sit at the foot of one had nothing left to sit against - it
 * followed the words at the top instead. */


/* A rule across a row.
 *
 * On the row's inner box, so it runs the whole measure the row occupies and
 * crosses whatever the layout puts in the way - a void column on a Swiss grid
 * is a column like any other, and a line drawn inside one of its neighbours
 * stops at the gap. This one does not know the columns are there.
 *
 * The site's own divider, from General, Dividers, so a rule on a row and a
 * Simple line in a column are the same line. */
.ds-row-rule-above > .ds-row-in{ border-top: var(--ds-rule, 1px solid var(--ds-line)) }
.ds-row-rule-below > .ds-row-in{ border-bottom: var(--ds-rule, 1px solid var(--ds-line)) }
.ds-row-rule-both > .ds-row-in{
  border-top: var(--ds-rule, 1px solid var(--ds-line));
  border-bottom: var(--ds-rule, 1px solid var(--ds-line));
}

/* A screen row is exactly a screen and the border is part of that screen, so
   it is counted inside the height rather than added to it. */
.ds-row-screen.ds-cols-stretch[class*='ds-row-rule-'] > .ds-row-in{ box-sizing: border-box }

/* ------------------------------------------------------------ a film in a line

   Everything here is in em, so the film is the height of the heading at
   whatever size Typography sets that level to, and it is still right at every
   breakpoint with no figure typed anywhere. That is the whole reason it belongs
   to the heading rather than being an Image element standing beside one.

   The shape is set rather than measured from the file: a video reports its own
   dimensions only once its metadata has arrived, so a box sized from the file
   is a box with no width until then - and a heading that re-wraps a beat after
   it is drawn is the layout shift this avoids. */
.ds-head-vid{
  display: inline-block;
  height: 1em;
  /* Sat on the baseline the film reads as hanging off the bottom of the line.
     A tenth of an em down puts its middle where the x-height is, which is where
     the eye expects a piece of a word to sit. */
  vertical-align: -0.14em;
  margin: 0 var(--ds-headvid-gap, 0.18em);
  line-height: 0;
  overflow: hidden;
  border-radius: 0.08em;
  --ds-grow-radius: 8px;
}
.ds-head-vid-16-9{ aspect-ratio: 16 / 9 }
.ds-head-vid-4-3 { aspect-ratio: 4 / 3 }
.ds-head-vid-1-1 { aspect-ratio: 1 / 1 }
.ds-head-vid-9-16{ aspect-ratio: 9 / 16 }
.ds-head-vid video{ width: 100%; height: 100%; display: block; object-fit: cover }

/* Growing under the pointer.
 *
 * A transform, so the line of type does not re-wrap as it grows - a width would
 * push the words along and the heading would reflow under the cursor. It lifts
 * over its neighbours while it is up, and the heading has to stop clipping it. */
.ds-head-vid-hover{
  transition: transform .45s cubic-bezier(.2,.7,.3,1);
  transform-origin: center center;
  position: relative;
  z-index: 1;
}
.ds-head-vid-hover:hover{ transform: scale(3.2); z-index: 6 }

/* ------------------------------------------------- a film that takes the screen

   The seat is the space the film leaves behind, so nothing on the page moves
   while it is out of the flow. Its size is written on it by the script from
   what was measured, which is why there is almost nothing to say here.

   The gap is the room it grows through. Made by the script rather than by a
   blank row in the layout: a row added underneath for an effect to use is a row
   that shows up in the builder, gets moved, gets deleted and takes the effect
   with it - and is still there taking up a screen of nothing once the effect is
   switched off. */
.ds-grow-seat{ position: relative }
.ds-grow-gap{ pointer-events: none }

/* Nothing is transitioned while it is growing.
 *
 * The scroll is what moves it, and a transition on the same properties is a
 * second thing moving it to somewhere the scroll has already left. The two
 * fight and the reader watches the film lag behind the page. */
.is-growing{ transition: none !important; overflow: hidden }
.is-growing video{ width: 100%; height: 100%; object-fit: cover; display: block }

@media (prefers-reduced-motion: reduce){
  .ds-head-vid-hover{ transition: none }
  .ds-head-vid-hover:hover{ transform: none }
}

/* ------------------------------------------------------- a wall of pictures

   Columns travelling past each other inside a window that clips them, so the
   wall reads as deeper than the page and nothing in it is the thing being
   looked at.

   The window is the element; the columns inside it are taller than it is and
   always will be, because each one holds its pictures twice. That second copy
   is what makes the travel endless, and it is why the height here is a window
   rather than a measurement of the contents. */
.ds-fall{
  display: grid;
  grid-template-columns: repeat(var(--ds-fall-cols, 3), minmax(0, 1fr));
  gap: var(--ds-fall-gap, 16px);
  overflow: hidden;
  position: relative;
}
.ds-fall-h-screen{ height: 100svh }
.ds-fall-h-tall  { height: 75svh }
.ds-fall-h-half  { height: 50svh }
.ds-fall-h-auto  { height: auto }

.ds-fall-col{ min-width: 0; overflow: hidden }

/* The run is what moves, not the pictures.
 *
 * One transform on one element per column, rather than a transform per picture:
 * a wall of forty pictures is four elements being moved, and the browser is
 * compositing four layers instead of forty. */
.ds-fall-run{
  display: flex;
  flex-direction: column;
  gap: var(--ds-fall-gap, 16px);
  will-change: transform;
}

/* The second copy is not a container of its own.
 *
 * It has to lay its pictures out in the same column flow as the first copy, or
 * the run would be the first copy plus one block holding all the rest - and the
 * gap between the two halves would be wrong, which is exactly the seam this
 * arrangement exists to hide. */
.ds-fall-again{
  display: flex;
  flex-direction: column;
  gap: var(--ds-fall-gap, 16px);
}

.ds-fall-run img,
.ds-fall-again img{
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--ds-fall-radius, 0);
}

/* A column that is not moving has no reason to be on a layer of its own. */
.ds-fall:not(.ds-fall-moving) .ds-fall-run{ will-change: auto }

@media (prefers-reduced-motion: reduce){
  .ds-fall-run{ will-change: auto }
}

/* ==============================================================
   Table element
   ==============================================================
 * Distinct from .ds-table above, which is the comparison table with its ticks
 * and its totals row and its own fixed answers about alignment. This one is
 * the element an author builds, so every one of those answers is a field. */

/* A table cannot reflow, so it slides inside its own box rather than pushing
   the page sideways. */
.ds-tbl-wrap{ width:100%; overflow-x:auto; -webkit-overflow-scrolling:touch }

.ds-tbl{
  width:100%;
  border-collapse:collapse;
  margin:0;
  /* Its own figure, so a table can be tightened without touching the list or
     the card sharing the page with it. Comes down with the screen like every
     other distance - see --ds-lo-space. */
  --ds-tbl-pad:max(calc(12px * var(--ds-lo-space, .3333)), .625vw);
}

.ds-tbl th, .ds-tbl td{
  padding:var(--ds-tbl-pad) calc(var(--ds-tbl-pad) * 1.6);
  text-align:inherit;
  border:0;
}
/* Nothing on the outside edges: a table indented from the column it sits in
   reads as a box rather than as part of the page. */
.ds-tbl th:first-child, .ds-tbl td:first-child{ padding-left:0 }
.ds-tbl th:last-child,  .ds-tbl td:last-child { padding-right:0 }

/* Headings take the weight the theme gives them and nothing else - no fill and
   no uppercasing the site did not ask for. */
.ds-tbl th{ font-weight:600 }

.ds-tbl-vtop    th, .ds-tbl-vtop    td{ vertical-align:top }
.ds-tbl-vmiddle th, .ds-tbl-vmiddle td{ vertical-align:middle }
.ds-tbl-vbottom th, .ds-tbl-vbottom td{ vertical-align:bottom }

/* Alignment belongs to the table and each cell inherits it, so it is set once
   rather than repeated per cell. The first column can say its own. */
.ds-tbl.ds-align-left    { text-align:left }
.ds-tbl.ds-align-center  { text-align:center }
.ds-tbl.ds-align-right   { text-align:right }
.ds-tbl.ds-align-justify { text-align:justify }
.ds-tbl th.ds-align-left  , .ds-tbl td.ds-align-left  { text-align:left }
.ds-tbl th.ds-align-center, .ds-tbl td.ds-align-center{ text-align:center }
.ds-tbl th.ds-align-right , .ds-tbl td.ds-align-right { text-align:right }

/* Figures line up under one another only if the digits are one width. */
.ds-tbl-figures{ font-variant-numeric:tabular-nums; font-feature-settings:"tnum" 1 }

/* Bottom borders rather than top, so the line under the headings is the same
   line as the one between two rows and there is never a double. */
.ds-tbl-between tbody tr:not(:last-child) > *,
.ds-tbl-between thead tr > *,
.ds-tbl-all tbody tr > *,
.ds-tbl-all thead tr > *,
.ds-tbl-head thead tr > *{
  border-bottom:var(--ds-tbl-bw, 1px) solid var(--ds-line, rgba(127,127,127,.28));
}
.ds-tbl-none tbody tr > *, .ds-tbl-none thead tr > *{ border-bottom:0 }

/* The rules, where the table's rows arrive rather than simply being there.
 *
 * A border cannot be drawn on - it is painted or it is not - so where a text
 * effect is set the border is handed over to a line of the cell's own that can
 * be scaled from nothing. Only then: a table with no effect keeps the border it
 * has always had and none of this applies to it.
 *
 * Scaled from the left, because the words above it arrive from the left as well
 * and a rule closing the other way reads as a separate idea. */
.ds-tbl-wrap[data-ds-tfx] .ds-tbl-between tbody tr:not(:last-child) > *,
.ds-tbl-wrap[data-ds-tfx] .ds-tbl-between thead tr > *,
.ds-tbl-wrap[data-ds-tfx] .ds-tbl-all tbody tr > *,
.ds-tbl-wrap[data-ds-tfx] .ds-tbl-all thead tr > *,
.ds-tbl-wrap[data-ds-tfx] .ds-tbl-head thead tr > *{
  border-bottom:0;
  position:relative;
}

.ds-tbl-wrap[data-ds-tfx] .ds-tbl-between tbody tr:not(:last-child) > *::after,
.ds-tbl-wrap[data-ds-tfx] .ds-tbl-between thead tr > *::after,
.ds-tbl-wrap[data-ds-tfx] .ds-tbl-all tbody tr > *::after,
.ds-tbl-wrap[data-ds-tfx] .ds-tbl-all thead tr > *::after,
.ds-tbl-wrap[data-ds-tfx] .ds-tbl-head thead tr > *::after{
  content:'';
  position:absolute;
  left:0; right:0; bottom:0;
  height:var(--ds-tbl-bw, 1px);
  background:var(--ds-line, rgba(127,127,127,.28));
  transform:scaleX(var(--ds-tbl-rule, 1));
  transform-origin:left center;
}

/* Nothing until the row is told to draw it.
 *
 * On the row rather than on its cells: the effect scales the row, and a value
 * written on the cell would be the cell answering for itself instead of taking
 * the row's. Set only once the row has been marked, so a table whose script
 * never runs shows every rule exactly as before. */
.ds-tbl-wrap[data-ds-tfx] tr.ds-tbl-ruled{ --ds-tbl-rule:0 }

/* ==============================================================
   Hover columns
   ==============================================================
 * A row of links where the one being pointed at is the one in color. The
 * choosing is :hover and :focus-within on the set and on each column, so
 * nothing decides what is lit except the pointer - it cannot get stuck lit
 * when the pointer leaves the window, and it works before any script runs. */

.ds-hcols{
  display:grid;
  grid-template-columns:repeat(var(--ds-hcol-n, 4), minmax(0, 1fr));
  gap:var(--ds-hcol-gap, var(--ds-gutter, 24px));
  --ds-hcol-fade:.35;
  --ds-hcol-shape:3 / 4;
}

.ds-hcol{
  display:flex; flex-direction:column; gap:max(.75rem, .625vw);
  min-width:0;
  /* Both, because the fade and the desaturation are separate settings and a
     column may be asked for either or both. */
  transition:opacity .32s ease, filter .32s ease;
}

/* A column is a link, so the theme's link colors reach it - and a:hover is
   heavier than a single class, which turned the title of the column being
   pointed at into the link hover color and made it vanish on a light page.
   Two classes and a pseudo-class outweigh it without !important. The columns
   are content that happens to be clickable, not links in a sentence, so they
   keep the color of the words around them in every state. */
.ds-hcols .ds-hcol:is(:link, :visited, :hover, :focus, :active){
  color:inherit;
  text-decoration:none;
}

/* Point at the set and everything drops; point at one and it comes back. The
   second rule needs no extra weight - it is a longer selector than the first,
   so it wins on its own. */
.ds-hcols:hover .ds-hcol,
.ds-hcols:focus-within .ds-hcol{ opacity:var(--ds-hcol-fade) }
.ds-hcols:hover .ds-hcol:hover,
.ds-hcols:focus-within .ds-hcol:focus{ opacity:1 }

.ds-hcols-grey:hover .ds-hcol,
.ds-hcols-grey:focus-within .ds-hcol{ filter:grayscale(1) }
.ds-hcols-grey:hover .ds-hcol:hover,
.ds-hcols-grey:focus-within .ds-hcol:focus{ filter:none }

/* The picture keeps its shape whatever the file is, so four columns of
   different photographs still line up along the bottom of the media. */
.ds-hcol-frame{
  display:block; width:100%; overflow:hidden;
  aspect-ratio:var(--ds-hcol-shape, 3 / 4);
  background:var(--ds-swiss-col-bg, var(--ds-card-bg, rgba(127,127,127,.08)));
  border-radius:inherit;
}
.ds-hcol-media{ display:block; width:100%; height:100%; object-fit:cover }
/* Shape auto means the file decides, and then a fixed height would crop it. */
.ds-hcols [style*="--ds-hcol-shape:auto"] .ds-hcol-frame{ aspect-ratio:auto }

.ds-hcol-title{ display:block; font-weight:600; line-height:1.25 }
.ds-hcol-text { display:block; line-height:1.45 }

/* The corner is set on the element and inherited by the frame, so one field
   answers for the media in every column. */
.ds-hcols.ds-corner-square .ds-hcol{ border-radius:0 }
.ds-hcols.ds-corner-soft   .ds-hcol{ border-radius:max(8px, .4166vw) }
.ds-hcols.ds-corner-round  .ds-hcol{ border-radius:max(24px, 1.25vw) }

/* Focus has to be visible on a link that carries no underline. */
.ds-hcol:focus-visible{ outline:2px solid currentColor; outline-offset:4px }

@media (prefers-reduced-motion:reduce){
  .ds-hcol{ transition:none }
}

/* ==============================================================
   List drawn as pills
   ==============================================================
 * A row of tags rather than a column of lines. Still the same list markup, so
 * the marker, the sizes and the alignment all still mean what they meant. */
.ds-list.ds-list-pills{
  display:flex; flex-wrap:wrap; gap:max(8px, .4166vw);
  list-style:none; margin:0; padding:0; columns:auto;
}
.ds-list-pills > li{
  display:inline-flex; align-items:center;
  padding:max(7px, .3645vw) max(16px, .8333vw);
  border-radius:999px;
  background:var(--ds-pill-bg, var(--ds-text, #111));
  color:var(--ds-pill-ink, var(--ds-bg, #fff));
  /* No rule and no marker offset: the shape is the separation. */
  border:0; margin:0;
}
.ds-list-pills > li::marker{ content:none }
.ds-list-pills.ds-align-center{ justify-content:center }
.ds-list-pills.ds-align-right { justify-content:flex-end }

/* ==============================================================
   Meters
   ==============================================================
 * A name and how full it is. The width is the reading, so the number does not
 * have to be printed for the row to say something. */
.ds-meters{
  display:flex; flex-direction:column;
  gap:var(--ds-meter-gap, max(8px, .4166vw));
  margin:0; padding:0;
  --ds-meter-h:max(28px, 1.4583vw);
}
.ds-meter{ display:block; position:relative }
.ds-meter dt, .ds-meter dd{ margin:0 }

.ds-meter-track{
  position:relative; overflow:hidden;
  min-height:var(--ds-meter-h);
  background:var(--ds-meter-track, rgba(127,127,127,.14));
}
.ds-meter-fill{
  display:block; height:100%; min-height:var(--ds-meter-h);
  background:var(--ds-meter-fill, rgba(127,127,127,.42));
  /* Grows from the leading edge whichever way the page reads. */
  transition:width .4s ease;
}

.ds-meters-round  .ds-meter-track, .ds-meters-round  .ds-meter-fill{ border-radius:999px }
.ds-meters-soft   .ds-meter-track, .ds-meters-soft   .ds-meter-fill{ border-radius:max(6px, .3125vw) }
.ds-meters-square .ds-meter-track, .ds-meters-square .ds-meter-fill{ border-radius:0 }

.ds-meter-name{ color:var(--ds-meter-ink, inherit) }

/* The name sitting on the bar rather than over it. Absolute so it does not
   stretch the track, and above the fill so it stays readable at any width. */
.ds-meters-inside .ds-meter{ display:grid }
.ds-meters-inside .ds-meter-name,
.ds-meters-inside .ds-meter-track{ grid-area:1 / 1 }
.ds-meters-inside .ds-meter-name{
  z-index:1; align-self:center;
  padding:0 max(16px, .8333vw);
  pointer-events:none;
}
.ds-meters-above .ds-meter-name{ margin-bottom:max(5px, .2604vw) }

.ds-meter-pc{ opacity:.62; margin-left:.4em; font-variant-numeric:tabular-nums }

@media (prefers-reduced-motion:reduce){
  .ds-meter-fill{ transition:none }
}

/* ==============================================================
   A quotation with a face beside it
   ==============================================================
 * Only reaches quotations that were given a picture, so every quotation
 * without one keeps the block layout it has always had. */

.ds-quote-has-pic{
  display:flex;
  align-items:flex-start;
  gap:var(--ds-quote-picgap, max(16px, .8333vw));
  --ds-quote-pic:max(88px, 4.5833vw);
}
/* The words take what is left, and may be narrower than their longest word
   without pushing the picture off the card. */
.ds-quote-has-pic .ds-quote-body{ flex:1 1 0; min-width:0 }
.ds-quote-has-pic .ds-quote-body > :last-child{ margin-bottom:0 }

.ds-quote-pic{
  flex:0 0 var(--ds-quote-pic);
  width:var(--ds-quote-pic);
  display:block;
  overflow:hidden;
  /* Square by default so a row of cards lines up whatever shape the files are.
     object-fit does the cropping rather than the file having to be square. */
  aspect-ratio:1 / 1;
  background:var(--ds-swiss-col-bg, var(--ds-card-bg, rgba(127,127,127,.08)));
}
.ds-quote-pic img{ display:block; width:100%; height:100%; object-fit:cover }

.ds-quote-pic-right{ flex-direction:row-reverse }
.ds-quote-pic-above{ flex-direction:column; align-items:stretch }
/* Standing above the words it is a picture across the card, not a stamp, so it
   takes the width and keeps a shape rather than a fixed size. */
.ds-quote-pic-above .ds-quote-pic{ flex:0 0 auto; width:100%; aspect-ratio:16 / 9 }

.ds-quote-has-pic.ds-corner-square .ds-quote-pic{ border-radius:0 }
.ds-quote-has-pic.ds-corner-soft   .ds-quote-pic{ border-radius:max(8px, .4166vw) }
.ds-quote-has-pic.ds-corner-round  .ds-quote-pic{ border-radius:999px }
/* A round crop only reads as round while it is square. */
.ds-quote-pic-above.ds-corner-round .ds-quote-pic{ border-radius:max(24px, 1.25vw) }

/* Side by side is a desktop arrangement. Below the phone breakpoint a stamp
   and a sentence sharing a line leave about twenty characters for the words. */
@media (max-width:480px){
  .ds-quote-pic-left, .ds-quote-pic-right{ flex-direction:column; align-items:flex-start }
}

/* A box filled the way a column set to Surface is. One chain, so the picker in
   Settings > General > Swiss layouts moves both. */
.ds-box-surface{ background-color:var(--ds-swiss-col-bg, var(--ds-card-bg, rgba(127,127,127,.08))) }
/* Same room as a painted column, for the same reason and from the same field. */
.ds-box-surface{ padding:var(--ds-swiss-col-pad, max(24px, 1.25vw)) }

/* ==============================================================
   Sound readout
   ============================================================== */
.ds-analyzer{
  display:inline-flex; align-items:center; gap:max(10px, .5208vw);
  appearance:none; background:none; border:0; padding:0; margin:0;
  font:inherit; color:var(--ds-an-ink, inherit);
  --ds-an-h:max(20px, 1.0416vw);
  --ds-an-w:max(56px, 2.9166vw);
}
button.ds-analyzer{ cursor:pointer }

.ds-analyzer-canvas{
  display:block; flex:0 0 auto;
  width:var(--ds-an-w); height:var(--ds-an-h);
}
/* A ring is measured on its shorter side, so it has to be square or it is an
   ellipse of lines. */
.ds-analyzer-ring .ds-analyzer-canvas,
.ds-analyzer-pulse .ds-analyzer-canvas{ width:var(--ds-an-h) }

.ds-analyzer-at-left { flex-direction:row-reverse }
.ds-analyzer-at-under{ flex-direction:column; align-items:flex-start }
.ds-analyzer-at-none .ds-analyzer-say{ display:none }

.ds-analyzer-say{ white-space:nowrap; opacity:.72; transition:opacity .2s ease }
.ds-analyzer.is-on .ds-analyzer-say{ opacity:1 }

.ds-analyzer:focus-visible{ outline:2px solid currentColor; outline-offset:4px; border-radius:4px }

.ds-analyzer.ds-align-center{ justify-content:center }
.ds-analyzer.ds-align-right { justify-content:flex-end }

/* A column that is nothing but a picture.
 *
 * The Swiss grid sets align-items:start, so a tile is as tall as what is in it
 * - which is right for a mass of type and wrong for a column whose only content
 * is its own background picture. There is nothing inside to give it height, so
 * it collapsed to the grid's minimum and drew the photograph as a 42px strip.
 *
 * Stretched, it takes the height of the row it is placed on and the picture
 * fills it, which is the only thing a column like this can have meant. */
.ds-row-in > .ds-col:not(:has(.ds-el)):is(
  :has(> .ds-col-pic),
  :has(> .ds-col-vid),
  :has(> .ds-col-in > .ds-col-pic),
  :has(> .ds-col-in > .ds-col-vid)
){
  align-self: stretch;
}

/* A picture is not a word.
 *
 * An img is inline by default, so it sits on the baseline of a line of text and
 * leaves the descender space under it inside its own figure - about 9px at this
 * size. Two figures with an equal 31px between them therefore showed 31px
 * across and 40px down, and the grid of squares read as uneven for a reason
 * nothing in the layout could explain. Block removes the line box entirely. */
.ds-figure img{ display: block }

