/* 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. */
@media (max-width:781px){
  .ds-row-in{ grid-template-columns:1fr }
  .ds-col,[class*="ds-col-"]{ grid-column:1 / -1 }
}

/* 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 }

/* 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. */
body:has(.ds-row-full),body:has(.ds-row-bleed){ overflow-x:clip }

.ds-row-full,.ds-row-bleed{ width:100vw; max-width:100vw;
  margin-inline:calc(50% - 50vw) }
.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 }

/* Rows carry no padding of their own. Space between sections is a gap between
   rows, not 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.

   The gap is on every row but the last, so the space before the footer comes
   from the page's bottom margin rather than from both at once. A row can still
   override its own padding from the builder. */
.ds-row{ padding-block:0 }

/* A row with a shader behind it. isolation so the canvas cannot slide under a
   neighbouring 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){ margin-inline:var(--ds-row-mx,0px) }

/* 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 centres 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% }

/* 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. */
body:not(.ds-has-title) .ds-main:has(> .ds-row:first-child.ds-row-screen),
body:not(.ds-has-title) .ds-main:has(> :first-child > .ds-row:first-child.ds-row-screen){ padding-top:0 }

/* 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. */
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{
  min-height:calc(100svh - var(--ds-header-h,80px) - var(--ds-header-pt,0px) - var(--ds-header-pb,0px)) }

/* 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. */
body.ds-has-bar .ds-main > .ds-row:first-child.ds-row-screen,
body.ds-has-bar .ds-main > :first-child > .ds-row:first-child.ds-row-screen{
  min-height:calc(100svh - var(--ds-bar-h,0px) - var(--ds-bar-line-w,0px)) }

body.ds-has-bar.ds-header-inline .ds-main > .ds-row:first-child.ds-row-screen,
body.ds-has-bar.ds-header-inline .ds-main > :first-child > .ds-row:first-child.ds-row-screen{
  min-height:calc(100svh - var(--ds-bar-h,0px) - var(--ds-bar-line-w,0px)
    - var(--ds-header-h,80px) - var(--ds-header-pt,0px) - var(--ds-header-pb,0px)) }

/* 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. */
.ds-row-screen{ min-height:calc(100svh - var(--ds-screenrow-top, 0px)) }

body.ds-has-bar .ds-main > .ds-row:first-child.ds-row-screen,
body.ds-has-bar .ds-main > :first-child > .ds-row:first-child.ds-row-screen,
body.ds-has-bar.ds-header-inline .ds-main > .ds-row:first-child.ds-row-screen,
body.ds-has-bar.ds-header-inline .ds-main > :first-child > .ds-row:first-child.ds-row-screen{
  min-height:calc(100svh - var(--ds-screenrow-top, 0px)) }

/* 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. */
.ds-row-hasmedia{ position:relative; isolation:isolate; overflow:hidden }
.ds-row-bg{ position:absolute; inset:0; width:100%; height:100%;
  z-index:0; pointer-events:none }
.ds-row-vid{ object-fit:cover }
.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
 * neighbour. 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) }

/* 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 colours.
 *
 * 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
 * colour above it is the other half of the decision.
 *
 * The gradient edge is two backgrounds rather than a border colour, 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 coloured box with a height: one
 * property carries both the thickness and the colour, and a thickness of zero
 * then draws nothing without needing to be special-cased.
 *
 * The colour is the site's divider colour, which is the whole reason this
 * element exists - the setting was there with nothing to draw. An element given
 * its own colour 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 centred, everything under it centred 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 }

/* 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
 * colour from Elements - falling back to the heading colour, which is what it
 * looked like before the colour 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-font-heading);
  color:var(--ds-kicker-ink, var(--ds-heading));
  font-size:var(--ds-kicker-size,14px);
  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,22px);
  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,14px);
  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);
}

.ds-figure{ margin:0 }
.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% }
.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 centred 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:clamp(16px,4vw,56px) }
.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 colour, alpha and all, so being faded a second time
   here both darkened a colour 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 }
/* Centred 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
 * catalogue 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%;
  max-height:calc(100svh - clamp(96px,16vw,190px)); 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,14px);
  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);
  border: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 }
.ds-lb-x:hover,.ds-lb-nav:hover{ background:rgba(255,255,255,.12);
  border-color:rgba(255,255,255,.4) }
.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:clamp(14px,3vw,28px); right:clamp(14px,3vw,28px) }
.ds-lb-nav{ top:50%; margin-top:calc(var(--ds-lb-icon,52px) / -2) }
.ds-lb-nav.prev{ left:clamp(10px,2.5vw,30px) }
.ds-lb-nav.next{ right:clamp(10px,2.5vw,30px) }
.ds-lb-nav[hidden]{ display:none }

.ds-lb-count{ position:absolute; bottom:clamp(14px,3vw,26px); left:50%;
  transform:translateX(-50%); color:rgba(255,255,255,.5); font-size:13px;
  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;
  max-height:calc(100svh - clamp(96px,16vw,190px));
  /* Unset, the padding stays fluid and the colours are the theme's. */
  padding:var(--ds-modal-pad, clamp(24px,4vw,48px));
  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 colour.
 *
 * They took the panel's text colour, 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) }

.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. */
.ds-lb-modal .ds-lb-x{
  color:var(--ds-modalx-ink, var(--ds-text,#fff));
  background:var(--ds-modalx-bg, rgba(255,255,255,.04));
  border-color:var(--ds-modalx-edge, var(--ds-line, rgba(255,255,255,.16))) }
/* Given a colour, 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, #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) }

/* 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:clamp(14px,3vw,28px) clamp(14px,3vw,28px) auto auto }
body.ds-modalx-at-tl .ds-lb-modal .ds-lb-x{
  inset:clamp(14px,3vw,28px) auto auto clamp(14px,3vw,28px) }
body.ds-modalx-at-br .ds-lb-modal .ds-lb-x{
  inset:auto clamp(14px,3vw,28px) clamp(14px,3vw,28px) auto }
body.ds-modalx-at-bl .ds-lb-modal .ds-lb-x{
  inset:auto auto clamp(14px,3vw,28px) clamp(14px,3vw,28px) }

/* 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, clamp(24px,4vw,48px))
        var(--ds-modal-pad, clamp(24px,4vw,48px)) auto auto }
body.ds-modalx-at-ptl .ds-lb-modal .ds-lb-x{
  inset:var(--ds-modal-pad, clamp(24px,4vw,48px)) auto auto
        var(--ds-modal-pad, clamp(24px,4vw,48px)) }
body.ds-modalx-at-pbr .ds-lb-modal .ds-lb-x{
  inset:auto var(--ds-modal-pad, clamp(24px,4vw,48px))
        var(--ds-modal-pad, clamp(24px,4vw,48px)) auto }
body.ds-modalx-at-pbl .ds-lb-modal .ds-lb-x{
  inset:auto auto var(--ds-modal-pad, clamp(24px,4vw,48px))
        var(--ds-modal-pad, clamp(24px,4vw,48px)) }

/* 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:13px; 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:clamp(14px,4vw,24px); margin-top:0 }
  .ds-lb-nav.prev{ left:calc(50% - 74px) }
  .ds-lb-nav.next{ right:calc(50% - 74px) }
  .ds-lb-count{ bottom:calc(clamp(14px,4vw,24px) + 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)) }
.ds-posts-masonry .ds-post-card{ break-inside:avoid;
  margin-bottom:var(--ds-gutter,24px) }

/* 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 }
@media (min-width:700px){
  /* The picture takes a third, so a list of posts reads as a list rather than
     as a stack of full width images with captions. */
  .ds-posts-list .ds-post-card:has(.ds-post-card-img){
    grid-template-columns:minmax(0,1fr) minmax(0,2fr); align-items:center }
}

/* 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 colour of their own where nothing is set, so a card
   left alone reads exactly as it did - the heading takes the page's heading
   colour 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:13px; 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 }
.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. Neighbours
 * 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
 * centred 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 centred the label inside the button. The setting said centre, the
 * button was centred, 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:14px; text-decoration:none; border:1px solid var(--ds-btn-bg,#fff);
  background:var(--ds-btn-bg,#fff); color:var(--ds-btn-text,#000);
  transition:opacity .15s ease }
/* 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 colour, 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 colours 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.
 *
 * Centred 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 centres it. */
  line-height: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. */
  text-box: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 colour declared on an element beats one it would have
     inherited, so the Text swatch on Elements and the element's own colour
     both did nothing. Read here as well, ahead of the heading colour 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 colour, which the mark inside it inherits. After the rule that
   colours 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 colour, which
   is what the separator took before it could be coloured 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 colour 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
   * colour 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 colour,
   * which is the control that exists for exactly this. */
  --ds-icon-ink:currentColor;
  /* Centred 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. Centring 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 colour like the words do. */
/* Sized in em so it follows the marquee's own type size, and left to take its
   colour 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 behaviour 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. */
.ds-marquee-slow   .ds-marquee-track{ animation-duration:16s }
.ds-marquee-medium .ds-marquee-track{ animation-duration:9s }
.ds-marquee-fast   .ds-marquee-track{ animation-duration: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) }

/* ---- 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 colour 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-font-fullwidth, var(--ds-font-heading));
  line-height:.9; font-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. */
  text-transform:var(--ds-rowbgtext-case, none);
  user-select:none;
}

/* Heading sized, the same fluid scale a hero uses. */
.ds-rowbgtext span{ padding-block:.16em }

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

/* 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) }
.ds-quote .ds-quote-text{ font-family:var(--ds-font-heading) }
.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 }
.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-accent) }
.ds-quote-by cite{ font-style:normal }
.ds-quote-who{ color:var(--ds-heading) }
/* A dash between the name and the role, in the role's own colour.
 *
 * On the role rather than after the name, so it takes the colour 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. */
.ds-quote-marks .ds-quote-text::before,
.ds-quote-marks .ds-quote-text::after{
  font-family:var(--ds-font-text); color:var(--ds-accent);
  line-height:.8; display:block }

/* The gap above the words and the gap below them are equal, and that takes two
   different margins.

   A quotation mark's ink sits high in its em box: at a 44px mark with
   line-height .8 the ink runs 4.9px to 14.9px from the box top, so the box hangs
   well below the mark itself.

   The words have the same problem in the other direction: their line box holds
   ink from 3.1px to 25.1px, so the box ends above where the letters do.

   Both matter, because the eye measures ink to ink and not box to box. Squaring
   only the mark left 29px over the words and 17px under them - which is what
   "the quote is not in the middle of the marks" was. What has to hold is
   margin-top minus margin-bottom = 0.494em; these two are that, landing both
   gaps on about 0.58em. */
.ds-quote-marks .ds-quote-text::before{ content:var(--ds-qopen,'\201C'); margin-bottom:.05em }
/* Both marks follow the quotation's own alignment. The closing one was forced
   to the right, which on a centred 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:.544em }

/* 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' }

.ds-qmsize-h1 .ds-quote-text::before,.ds-qmsize-h1 .ds-quote-text::after{ font-size:var(--ds-h1-size) }
.ds-qmsize-h2 .ds-quote-text::before,.ds-qmsize-h2 .ds-quote-text::after{ font-size:var(--ds-h2-size) }
.ds-qmsize-h3 .ds-quote-text::before,.ds-qmsize-h3 .ds-quote-text::after{ font-size:var(--ds-h3-size) }
.ds-qmsize-h4 .ds-quote-text::before,.ds-qmsize-h4 .ds-quote-text::after{ font-size:var(--ds-h4-size) }


/* ---- 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. */
.ds-inrow{ display:grid; grid-template-columns:repeat(12,1fr);
  gap:var(--ds-gutter,24px); width:100% }
.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 colour 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
   colour 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 colour.
 *
 * 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 colour
 * 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{
  margin:0;
  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 }

.ds-list{
  margin:0; padding:0;
  list-style:none;
  column-gap:var(--ds-gutter,24px);
}

.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 colour set on the list itself still wins over it, and a
   list on a site that has set neither is the text colour it stands in.

   No opacity. Seven tenths of a colour somebody chose is a colour 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 colour where the Elements tab has set one, and takes
   the text colour 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 colour 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. */

/* Centred 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 }

@media (max-width:700px){
  /* Two or three columns of list on a phone is one word per line. */
  .ds-list{ columns:1 !important }
}

/* 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;
}

.ds-slide{
  position:absolute; inset:0;
  display:grid;
  opacity:0; visibility:hidden;
  transition:opacity .8s cubic-bezier(.2,0,.2,1);
}
.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;
}

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

.ds-slide-words{
  position:relative; z-index:1;
  display:grid; gap:clamp(12px,1.6vw,22px);
  align-content:center;
  padding:clamp(24px,6vw,90px);
  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:clamp(16px,3vw,34px);
  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. */
@media (max-width:700px){
  .ds-slide-words{ max-width:100%; padding:clamp(20px,7vw,40px) }
}

/* 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 colours.
 * ------------------------------------------------------------------ */
.ds-panels{ --ds-tab-radius:0px; --ds-tab-bw:1px }

/* Two edges, drawn from two figures. Where a site sets only one colour 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 colour 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 colour. */
.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 }
}
