/*
Theme Name: DLO
Theme URI: https://dlosolutions.com
Author: David Lopez
Description: Purpose-built block theme for dlosolutions.com. Dark editorial
  surface with a single accent, built to carry evidence tables rather than
  marketing copy.
Requires at least: 6.5
Tested up to: 7.1
Requires PHP: 8.0
Version: 1.0.11
License: GPL-2.0-or-later
Text Domain: dlo
*/

/*
Design notes.

The direction is a dark editorial surface with one accent. Lime on black is
close to the default developer-portfolio look, so the accent is pulled off
stock chartreuse and the distinctiveness is spent where it earns something:
on the instrument label register and on the data components.

The instrument label is the signature. Every number on this page is followed
by the thing a sceptical reader actually wants, which is where it came from:
the window, the source, the sample, the caveat. Those strings all wear the
same treatment, in mono, uppercase, widely tracked and deliberately small,
so the page reads as a set of measurements with their provenance attached
rather than as claims. It is the one visual idea the page repeats, and it is
the one that matches what the site is arguing.

Everything else stays quiet. No gradients, no glows, no card shadows, one
accent held back for figures and links, hairlines for structure.
*/

/* Foundations --------------------------------------------------------- */

/* Dark scrollbars and form controls, so the browser furniture matches. */
html { color-scheme: dark; }

/* Long URLs and table headers must not push the page sideways. */
body { overflow-wrap: break-word; }

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

/* The stat tiles link into the case studies further down the page, so give
   every anchor target room to breathe under the sticky-free header. */
[id] { scroll-margin-block-start: var(--wp--preset--spacing--40); }

/* The instrument label register ---------------------------------------
   Provenance, windows, sources and units. Small, mono, tracked, muted.
   This is the page's signature, so it is defined once and reused. */
.dlo-hero__eyebrow,
.dlo-hero__location,
.dlo-tile__window,
.dlo-case__label,
.dlo-case__note,
.dlo-cred__meta,
.dlo-cred__issuer,
.dlo-table caption,
.dlo-table thead th {
  font-family: var(--wp--preset--font-family--mono);
  font-size: 0.75rem;
  line-height: 1.5;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--wp--preset--color--muted);
}

/* Layout -------------------------------------------------------------- */
.dlo-page { max-width: 68rem; margin-inline: auto; padding: var(--wp--preset--spacing--50) var(--wp--preset--spacing--40); }
.dlo-page > section { margin-block-start: var(--wp--preset--spacing--60); }

/* Prose measure. Evidence needs a readable line length even though the
   grid components run to the full 68rem. */
.dlo-page > section > p,
.dlo-bio p,
.dlo-faq__item p { max-width: 68ch; }

/* Sections announce themselves with a short accent rule rather than a
   number. The content is not a sequence, so numbering it would lie. */
.dlo-page > section > h2 {
  margin-block: 0 var(--wp--preset--spacing--30);
  padding-block-start: var(--wp--preset--spacing--30);
  border-block-start: 1px solid var(--wp--preset--color--line);
}
.dlo-page > section > h2::before {
  content: "";
  display: block;
  inline-size: 2.5rem;
  block-size: 2px;
  background: var(--wp--preset--color--accent);
  margin-block-end: var(--wp--preset--spacing--30);
  margin-block-start: -0.0625rem;
}

/* Hero ---------------------------------------------------------------- */
.dlo-hero { display: grid; gap: var(--wp--preset--spacing--40); grid-template-columns: 1fr; align-items: end; }
/* Portrait column widened 2026-09-02, David: the photo was not using the
   space. It was capped at 22rem inside a 0.9fr column, so it rendered about
   350px in a 390px track and left a dead margin either side of it. */
@media (min-width: 56rem) { .dlo-hero { grid-template-columns: 1.25fr 1fr; } .dlo-tiles { grid-column: 1 / -1; } }

.dlo-hero__eyebrow { margin: 0 0 var(--wp--preset--spacing--20); color: var(--wp--preset--color--accent); }
.dlo-hero__title { margin: 0 0 var(--wp--preset--spacing--30); }
.dlo-hero__positioning { font-size: var(--wp--preset--font-size--large); line-height: 1.5; color: var(--wp--preset--color--bright); max-width: 44ch; }
.dlo-hero__location { margin-block: var(--wp--preset--spacing--30) 0; }
.dlo-hero__actions { display: flex; flex-wrap: wrap; gap: var(--wp--preset--spacing--20); margin-block-start: var(--wp--preset--spacing--40); }

/* The portrait is graded to the palette by scripts/prepare_portrait.py.
   The bottom fade is kept here rather than baked into the asset so the
   mount stays reversible and the source file stays honest. */
.dlo-hero__portrait img {
  width: 100%;
  /* 30rem is 480px, and the source is 560px wide, so the image still renders
     under its natural size and never upscales into softness. Raising this
     past 35rem would. */
  max-width: 30rem;
  margin-inline: auto;
  display: block;
  -webkit-mask-image: linear-gradient(to bottom, #000 55%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 55%, transparent 100%);
}

.dlo-btn {
  display: inline-block;
  padding: 0.6rem 1.1rem;
  border: 1px solid var(--wp--preset--color--accent);
  border-radius: 2rem;
  background: var(--wp--preset--color--accent);
  color: var(--wp--preset--color--ink) !important;
  text-decoration: none;
  font-weight: 600;
}
.dlo-btn--ghost { background: transparent; color: var(--wp--preset--color--accent) !important; border-color: var(--wp--preset--color--line); }
.dlo-btn:hover { background: var(--wp--preset--color--bright); border-color: var(--wp--preset--color--bright); }
.dlo-btn--ghost:hover { background: transparent; border-color: var(--wp--preset--color--accent); }

/* Stat tiles ----------------------------------------------------------
   The tile contract is value, metric, entity, window. The value is a
   standalone display figure, so it keeps the font's proportional figures;
   tabular figures are for columns that have to line up, and they leave a
   figure like 2.2x looking loose at this size. */
/* Rows are explicit, not just the `gap` shorthand. Until 2026-08-30 every
   page carried exactly three tiles, so the grid never produced a second row
   and the row axis was never exercised. Now that pages carry up to six,
   row-gap and align-content are stated outright rather than inherited from a
   shorthand, and align-content:start stops the rows stretching to fill any
   leftover height in the hero row they sit in. */
.dlo-tiles {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  column-gap: var(--wp--preset--spacing--30);
  align-content: start;
  /* Row separation is NOT row-gap. See .dlo-tile below. The negative margin
     absorbs the trailing padding under the final row so the hero's own gap
     is not doubled. */
  row-gap: 0;
  margin-block-end: calc(-1 * var(--wp--preset--spacing--40));
}

/* THREE PER ROW on wide screens, 2026-09-02, David's call. auto-fit at
   minmax(13rem, 1fr) resolved to four columns inside the 68rem page, which
   left the six-tile pages rendering 4 + 2: a full row and a stub. Three
   divides both six-tile pages evenly and leaves the PPC page's three on one
   line, and the wider track gives the long windows ("28-day windows either
   side of the Jun 2026 rebuild, GSC") room to set on fewer lines.

   The hub is the one page this does not divide: it carries four tiles and now
   renders 3 + 1. Left as is rather than padded to six with a weaker figure,
   which would be decorating a layout with evidence.

   auto-fit stays for narrow screens, where the minmax is what keeps the tiles
   from being squeezed below a readable width. */
@media (min-width: 56rem) {
  .dlo-tiles { grid-template-columns: repeat(3, 1fr); }
}

/* Row separation lives on the tile as padding, not on the grid as row-gap.
   Reported 2026-08-30: rows sat flush against each other in David's browser
   while the column gaps rendered correctly, so `row-gap` specifically was
   being lost. Rather than diagnose whose stylesheet was winning, the spacing
   moved somewhere a `gap` declaration cannot reach it.
   The visible card is the <a>/<div> inside, and block-size:100% resolves
   against the li's CONTENT box, so the padding shows as clean space between
   bands and the cards stay flush to their track. */
.dlo-tile {
  display: grid;
  min-inline-size: 0;
  padding-block-end: var(--wp--preset--spacing--40);
}
/* A tile whose figure has no evidence block to point at renders as
   .dlo-tile__static instead of an anchor. Same box, no link affordance: the
   hover transition and the pointer belong only to the ones that go
   somewhere. */
.dlo-tile a,
.dlo-tile__static {
  display: grid;
  gap: 0.15rem;
  /* NO block-size: 100%. It was here to make short cards match tall ones in a
     row, and .dlo-tile being display:grid already does that: the card is a
     grid item and stretches by default.
     Worse, the percentage was cyclic. The card's height resolved against the
     li, whose height came from the grid row, whose size was computed from the
     li's contribution with that percentage treated as auto. The row came out
     6px short of the card's real content, every card overflowed its own li by
     6px, and each one landed on the row below. Reported 2026-08-30. It only
     showed once a page had two rows, and it was invisible to any check that
     measured the li instead of the card inside it. */
  padding: var(--wp--preset--spacing--30);
  background: var(--wp--preset--color--raised);
  /* Not --line: the tile is interactive, so its border needs 3:1 contrast
     on ink. #636C71 clears that (3.63:1); --line (1.54:1) is decorative-only
     and stays as-is for tables and other non-interactive rules. The static
     tile keeps the same border so a row of tiles reads as one set. */
  border: 1px solid #636C71;
  border-radius: 0.75rem;
}
.dlo-tile a { text-decoration: none; transition: border-color 120ms ease; }
.dlo-tile a:hover { border-color: var(--wp--preset--color--muted); }
.dlo-tile__value  { font-size: var(--wp--preset--font-size--xlarge); font-weight: 700; line-height: 1.1; color: var(--wp--preset--color--accent); }
.dlo-tile__metric { color: var(--wp--preset--color--bright); font-weight: 600; }
.dlo-tile__entity { color: var(--wp--preset--color--body); font-size: var(--wp--preset--font-size--small); }
.dlo-tile__window { margin-block-start: 0.35rem; }

/* Charts ---------------------------------------------------------------
   Generated from facts.yml by build/charts.py. The figure is a lint block,
   so its caption carries the caveat; see build/site_lint.py BLOCK_TAGS. */
/* Capped the same as .dlo-table just above it in the page (its own cap is
   commented where it's declared): held short of the 68rem grid so the
   chart's marks don't stretch on a wide viewport. This is also load-bearing
   for the geometry in build/charts.py -- see that file's _BAR_H/_CORNER_R
   comment, which sizes the bar thickness and corner radius against this
   exact cap. Change one, check the other. */
.dlo-chart { margin: 0 0 var(--wp--preset--spacing--40); max-width: 46rem; }
.dlo-chart svg { inline-size: 100%; block-size: auto; overflow: visible; }
.dlo-chart__caption {
  margin-block-start: var(--wp--preset--spacing--20);
  color: var(--wp--preset--color--muted);
  font-size: var(--wp--preset--font-size--small);
}
/* SVG <text> does not inherit the page font by default, so it is set
   explicitly here rather than left to the browser's serif default. Fill
   colour is set per-element in build/charts.py, since it differs between
   the row label, the value and the focus/context bars.

   This rule is what fonts the hand-authored diagrams too. Until
   2026-09-01 each of their 18 <text> elements carried this exact
   declaration again as an inline style attribute, written back when they
   were loaded through an <img> and could not see this stylesheet at all.
   Inlined, they can, and an inline style beats a stylesheet rule, so the
   leftover attributes were quietly pinning the diagrams outside the theme
   rule they now follow. Removed. */
.dlo-chart svg text { font-family: var(--wp--preset--font-family--sans); }
.dlo-chart__row-label { font-size: 0.85rem; }
.dlo-chart__value { font-size: 0.85rem; font-weight: 600; }

/* Hand-authored diagrams (theme/dlo/assets/diagrams), a MODIFIER on
   .dlo-chart, not a change to it: the two generated charts above still
   need the full 46rem so their marks read at a useful size, and this
   class must never touch them.

   The two diagrams are drawn on a single stacked column (see their own
   comments in build/site_context.py) so their desktop and mobile render
   sizes are locked together by a fixed ratio: at .dlo-chart's 46rem
   (736px) cap, an 18-unit label on a 400-unit viewBox comes out at 736 *
   18/400 = 33.1px, and a diagram tall enough to hold five stacked rows
   at that scale runs past 1200px high, well over a screen (the same
   defect class rejected on the rebuild chart at 1899px). Narrowing the
   CONTAINER is the only lever that bounds height without redrawing:
   21rem (336px) keeps the smallest label at 336 * 18/400 = 15.1px,
   comfortably clear of --wp--preset--font-size--small (14px), while
   it-restructure.svg's 400x690 viewBox renders at 336 * 690/400 =
   579.6px tall, under the 620px ceiling. Mobile is unaffected: the
   .dlo-case column is already narrower than 21rem below the 40rem
   breakpoint, so this cap never binds there. */
.dlo-chart--diagram { max-width: 21rem; }

/* Case studies --------------------------------------------------------
   The callout is the one big figure per case: the number, what it measures,
   and how it was measured, in that order and in three different registers. */
/* SPACING ADDED 2026-09-02, David: the cards looked crowded. They carried no
   vertical padding and no margin at all, so six of them stacked flush and
   their accent rules joined into one unbroken line down the page. That read
   as a single block of six headings rather than six separate case studies.

   The gap is what divides them. Breaking the rule into one segment per card
   is a stronger signal than a border between them would be, and it avoids an
   L-shaped corner where a top border would meet the accent rule. */
.dlo-case {
  border-inline-start: 3px solid var(--wp--preset--color--accent);
  padding-inline-start: var(--wp--preset--spacing--30);
  padding-block: var(--wp--preset--spacing--40);
}
/* 3rem between cards against 1.75rem inside them. The gap has to beat the
   card's own internal spacing or the eye groups the wrong things: at 1.75rem
   each way, the space between two cards read the same as the space between a
   callout and its heading. */
.dlo-case + .dlo-case { margin-block-start: var(--wp--preset--spacing--50); }
@media (min-width: 40rem) { .dlo-case { padding-inline-start: var(--wp--preset--spacing--40); } }
.dlo-case__callout { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.6rem; margin: 0 0 var(--wp--preset--spacing--20); }
.dlo-case__stat  { font-size: var(--wp--preset--font-size--display); font-weight: 700; line-height: 1; color: var(--wp--preset--color--accent); }
.dlo-case__label { color: var(--wp--preset--color--bright); }
.dlo-case__note::before { content: "\2022"; margin-inline-end: 0.6rem; }
/* The case study already has its accent rule down the left edge, so it does
   not take the section rule as well. The selector covers <details> since
   2026-09-01, when the cards became accordions, and keeps `section` so a
   cached page rendered before that still styles correctly. */
.dlo-page > details.dlo-case h3 { margin-block: 0; border-block-start: 0; padding-block-start: 0; }

/* The cards' own section heading. It is a direct child of .dlo-page rather
   than of a <section>, so it does not inherit .dlo-page > section > h2 and
   takes the accent rule and top border explicitly. */
.dlo-cases__heading {
  margin-block: 0 var(--wp--preset--spacing--30);
  padding-block-start: var(--wp--preset--spacing--30);
  border-block-start: 1px solid var(--wp--preset--color--line);
}
.dlo-cases__heading::before {
  content: ""; display: block; inline-size: 2.5rem; block-size: 2px;
  background: var(--wp--preset--color--accent);
  margin-block-end: var(--wp--preset--spacing--20);
}

/* HEADING SCALE INSIDE A CASE STUDY, set here because the cards dropped a
   level on 2026-09-01 and theme.json defines h1 to h3 only. Section heading
   at xlarge, card title at large, stage headings a step under it. Until now
   the card titles rendered at the same size as Capabilities and fought it
   for the reader's eye. */
.dlo-case__summary h3 { font-size: var(--wp--preset--font-size--large); line-height: 1.25; font-weight: 650; }
.dlo-case h4 {
  /* 1.125rem, not the medium preset. Medium is the body size, so a stage
     heading set from it tied with the paragraph under it and leaned on
     weight and colour alone to read as a heading. This sits between body at
     1rem and the card title at 1.25rem. */
  font-size: 1.125rem;
  line-height: 1.35; font-weight: 650;
  color: var(--wp--preset--color--bright);
  margin-block: var(--wp--preset--spacing--30) var(--wp--preset--spacing--20);
}

/* --- Case study accordions -------------------------------------------
   Native <details>, no script. The whole summary is the hit target, so it
   gets the pointer and the focus ring rather than the heading alone. */
/* Shared by the case study cards and the FAQ: both are <details> whose
   <summary> wraps an h3. Listed together so the caret, the hover and the
   focus ring cannot drift apart between the two. */
.dlo-case__summary,
.dlo-faq__q { cursor: pointer; list-style: none; display: block; padding-block: var(--wp--preset--spacing--20); }
.dlo-case__summary::-webkit-details-marker,
.dlo-faq__q::-webkit-details-marker { display: none; }
.dlo-case__summary:focus-visible,
.dlo-faq__q:focus-visible { outline: 2px solid var(--wp--preset--color--accent); outline-offset: 4px; }
.dlo-case__summary:hover h3,
.dlo-faq__q:hover h3 { color: var(--wp--preset--color--accent); }
.dlo-case__summary h3,
.dlo-faq__q h3 { transition: color 120ms ease; }

/* The disclosure triangle, drawn rather than inherited, so it sits with the
   heading and turns with the state. */
.dlo-case__summary h3::after,
.dlo-faq__q h3::after {
  content: ""; display: inline-block; inline-size: 0.5em; block-size: 0.5em;
  margin-inline-start: 0.6em; vertical-align: middle;
  border-inline-end: 2px solid var(--wp--preset--color--accent);
  border-block-end: 2px solid var(--wp--preset--color--accent);
  transform: rotate(45deg) translate(-0.1em, -0.1em);
  transition: transform 150ms ease;
}
details.dlo-case[open] > .dlo-case__summary h3::after,
details.dlo-faq__item[open] > .dlo-faq__q h3::after { transform: rotate(-135deg) translate(-0.1em, -0.1em); }

/* Closed rows stay more compact than open ones, so a stack of them still
   reads as a list of choices rather than as spaced-out sections.
   NO LONGER ZERO, 2026-09-02: it was `padding-block: 0`, which put the
   summary flush against the top and bottom of its own accent rule. With six
   cards on the SEO page that read as crowded, which was David's word for it.
   A smaller padding than the open state keeps the original intent without
   the rows touching. */
details.dlo-case:not([open]) { padding-block: var(--wp--preset--spacing--30); }
details.dlo-case[open] { padding-block-end: var(--wp--preset--spacing--30); }

@media (prefers-reduced-motion: reduce) {
  .dlo-case__summary h3, .dlo-case__summary h3::after,
  .dlo-faq__q h3, .dlo-faq__q h3::after { transition: none; }
}
.dlo-case__summary h3::before { content: none; }

/* Tables --------------------------------------------------------------
   Wide content scrolls inside its own container, never the page body.
   functions.php wraps every table in .dlo-tablewrap, a focusable scroll
   region, which keeps the table element a real table for assistive
   technology. The rules on .dlo-table itself are the fallback for the
   case where that filter has not run.

   Furniture is recessive: hairline row rules, no vertical rules, no zebra
   striping, no accent. The accent belongs to the figures, not the grid. */
/* Held well short of the 68rem grid. A three column table stretched to the
   full page width puts a metre of empty space between the row label and its
   number, which is a long way for an eye to travel to check a figure. */
.dlo-table {
  width: 100%;
  max-width: 46rem;
  border-collapse: collapse;
  display: block;
  overflow-x: auto;
}
.dlo-tablewrap { max-width: 46rem; overflow-x: auto; }
.dlo-table, .dlo-tablewrap { margin-block: var(--wp--preset--spacing--40); }
/* No min-width. Left alone the table settles at its min-content width, which
   on a phone is a little over 300px, so the wrapper scrolls by a few dozen
   pixels instead of the few hundred a forced minimum would cost. */
.dlo-tablewrap > .dlo-table { display: table; overflow: visible; max-width: none; margin-block: 0; }
.dlo-tablewrap:focus-visible { outline: 2px solid var(--wp--preset--color--accent); outline-offset: 3px; }

/* The caption carries the source, the window and the caveat. It is prose,
   so it keeps the mono voice but drops the uppercase. */
.dlo-table caption {
  text-align: start;
  text-transform: none;
  font-size: 0.8125rem;
  letter-spacing: 0.01em;
  line-height: 1.6;
  max-width: 68ch;
  padding-block-end: var(--wp--preset--spacing--30);
}
.dlo-table th, .dlo-table td { padding: 0.6rem 0.9rem; border-block-end: 1px solid var(--wp--preset--color--line); text-align: start; }
.dlo-table th:first-child, .dlo-table td:first-child { padding-inline-start: 0; }
.dlo-table thead th { padding-block-end: 0.4rem; border-block-end-color: var(--wp--preset--color--muted); }
.dlo-table tbody th { color: var(--wp--preset--color--bright); font-weight: 600; }
.dlo-table tbody tr:last-child th, .dlo-table tbody tr:last-child td { border-block-end: 0; }

/* Every td in these tables is a measurement. Columns of numbers get
   tabular figures and a right edge so the digits line up. */
.dlo-table td,
.dlo-table thead th:not(:first-child) { text-align: end; font-variant-numeric: tabular-nums; }

/* Body copy blocks ----------------------------------------------------- */
.dlo-bio p:first-child { font-size: var(--wp--preset--font-size--large); color: var(--wp--preset--color--bright); line-height: 1.5; }
.dlo-writing p, .dlo-contact p, .dlo-certs p, .dlo-experience p { max-width: 68ch; }
.dlo-certs p { font-variant-numeric: tabular-nums; }

/* Lists ---------------------------------------------------------------- */
/* Bulleted, not carded. These are lists of things, and the skills blocks a
   few sections down were already bulleted, so the card treatment made two
   kinds of list on one page look like two different kinds of content. */
.dlo-tracks__list, .dlo-tools__list {
  margin: var(--wp--preset--spacing--30) 0 0;
  padding-inline-start: 1.2rem;
  max-width: 68ch;
}
.dlo-tracks__list li, .dlo-tools__list li { margin-block-end: var(--wp--preset--spacing--30); }
.dlo-tracks__list li::marker, .dlo-tools__list li::marker { color: var(--wp--preset--color--accent); }
.dlo-tracks__list strong, .dlo-tools__list strong { color: var(--wp--preset--color--bright); }
.dlo-tracks__list > li, .dlo-tools__list > li { padding-block-start: var(--wp--preset--spacing--30); border-block-start: 1px solid var(--wp--preset--color--line); }
.dlo-tracks__list h3, .dlo-tools__list h3 { margin: 0 0 var(--wp--preset--spacing--20); }
.dlo-tracks__list p, .dlo-tools__list p { margin: 0; color: var(--wp--preset--color--body); }

/* Employer names on the hub. NOT the .dlo-tracks__list treatment above: that
   puts a rule and a block of spacing on every item, which suits a name plus a
   sentence and would run most of a screen for eight bare names. Wrapped
   inline instead, so the list reads as one set at a glance. */
.dlo-experience__list {
  list-style: none;
  margin: var(--wp--preset--spacing--30) 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
  max-width: 68ch;
}
.dlo-experience__list li {
  padding: 0.3rem 0.7rem;
  background: var(--wp--preset--color--raised);
  border: 1px solid var(--wp--preset--color--line);
  border-radius: 0.5rem;
  color: var(--wp--preset--color--bright);
  font-size: var(--wp--preset--font-size--small);
}

.dlo-skills__block { margin-block-start: var(--wp--preset--spacing--40); }
.dlo-skills__block h3 { margin: 0 0 var(--wp--preset--spacing--20); }
.dlo-skills__block ul { margin: 0; padding-inline-start: 1.1rem; }
.dlo-skills__block li { margin-block-end: 0.35rem; }
.dlo-skills__block li::marker { color: var(--wp--preset--color--accent); }

/* Credentials ---------------------------------------------------------
   The page's whole argument is that every credential is checkable, so the
   tile is built to end on its verification link. Name, then the issuer's
   own record in the instrument register, then the link, in that order and
   in three registers: sans for the name, mono for the provenance, accent
   for the one thing to click.

   Two plate treatments, and the difference is factual rather than
   decorative. A credential with an artefact on file is MOUNTED: the badge
   or the certificate sits on a light plate, the one place this page goes
   light, because a seal is a physical thing and the dark surface would
   swallow it. Every Google Skillshop badge is dark artwork on a fully
   transparent background, so this is not a stylistic preference; flatten
   one onto the page and it disappears.

   A credential with no artefact is TYPESET: the year and the issuer set in
   the same mono register the rest of the page uses for provenance, on the
   raised surface with a hairline. Anthropic issues no badges at all, so
   twelve credentials wear this by design. That is what keeps the single
   image-less credential in the third group from reading as a hole in the
   grid, and it is why a scan arriving later needs no new CSS: add the
   badge key and the tile mounts instead. */
.dlo-creds__grid {
  list-style: none;
  margin: var(--wp--preset--spacing--40) 0 0;
  padding: 0;
  display: grid;
  gap: var(--wp--preset--spacing--40);
  grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
}
.dlo-cred { display: grid; align-content: start; gap: 0.3rem; }

/* Plate, name, provenance, link: four rows, aligned across the whole grid
   rather than per tile. Without this a two-line credential name shunts its
   date and its verify link a line lower than the tile beside it, and a
   reader comparing dates down a column has to hunt for each one. Subgrid
   is the only way to share row heights between separate grid items;
   without it the tiles simply stack at their natural heights, which is
   what they did before. */
@supports (grid-template-rows: subgrid) {
  .dlo-creds__grid { grid-auto-rows: auto; }
  .dlo-cred { grid-row: span 4; grid-template-rows: subgrid; }
}

.dlo-cred__plate {
  block-size: 8rem;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 0.2rem;
  padding: 0.75rem;
  margin-block-end: var(--wp--preset--spacing--20);
  border-radius: 0.5rem;
  background: var(--wp--preset--color--bright);
}
/* The height cap is in rem, not a percentage. `max-block-size: 100%` looks
   like the obvious way to fit a badge to its plate and silently does
   nothing: the plate centres its content, so the grid area it resolves
   against has no definite height, and a 320px square badge renders at the
   column's full width and spills over the credential name below it. This
   is 8rem of plate less 2 x 0.75rem of padding. */
.dlo-cred__plate img {
  max-block-size: 6.5rem;
  max-inline-size: 100%;
  inline-size: auto;
  object-fit: contain;
}

/* A rasterised certificate carries its own background, so the light plate the
   transparent badges need would frame a coloured card in white. Let it fill
   the plate edge to edge instead. */
.dlo-cred__plate--cert {
  padding: 0;
  background: transparent;
  overflow: hidden;
}
.dlo-cred__plate--cert img {
  max-block-size: none;
  block-size: 100%;
  inline-size: 100%;
  object-fit: cover;
}

.dlo-cred__plate--typeset {
  background: var(--wp--preset--color--raised);
  border: 1px solid var(--wp--preset--color--line);
}
/* The value-and-label contract the stat tiles use, at a quieter volume:
   the figure in the accent, the thing it names underneath it in mono. */
.dlo-cred__year {
  font-size: var(--wp--preset--font-size--large);
  font-weight: 700;
  line-height: 1;
  color: var(--wp--preset--color--accent);
}
.dlo-cred__issuer { text-align: center; text-wrap: balance; }

.dlo-cred__name { margin: 0; font-size: var(--wp--preset--font-size--medium); line-height: 1.35; }
.dlo-cred__meta { margin: 0; text-transform: none; letter-spacing: 0.01em; font-size: 0.6875rem; }
.dlo-cred__verify { margin: 0.15rem 0 0; font-size: var(--wp--preset--font-size--small); }
/* Long verification URLs never widen a grid column. */
.dlo-cred__verify a { text-underline-offset: 0.2em; overflow-wrap: anywhere; }
.dlo-cred__note {
  margin: 0.15rem 0 0;
  font-size: var(--wp--preset--font-size--small);
  color: var(--wp--preset--color--muted);
  border-inline-start: 2px solid var(--wp--preset--color--line);
  padding-inline-start: 0.6rem;
}

.dlo-creds__wallet { margin-block: var(--wp--preset--spacing--20) 0; }

.dlo-faq__item { border-block-start: 1px solid var(--wp--preset--color--line); padding-block-start: 0; margin-block-start: 0; }
details.dlo-faq__item[open] { padding-block-end: var(--wp--preset--spacing--30); }
/* The h3 sits inside the <summary>, which supplies the vertical rhythm, so
   it no longer carries a bottom margin of its own. */
.dlo-faq__q h3 { margin: 0; }
details.dlo-faq__item[open] > .dlo-faq__q h3 { margin-block-end: var(--wp--preset--spacing--20); }
.dlo-faq__item p { margin: 0; }

/* Site chrome ---------------------------------------------------------- */
.wp-block-site-title a { color: var(--wp--preset--color--bright); text-decoration: none; font-weight: 600; }
.wp-site-blocks > header { border-block-end: 1px solid var(--wp--preset--color--line); }
.wp-site-blocks > footer { border-block-start: 1px solid var(--wp--preset--color--line); margin-block-start: var(--wp--preset--spacing--60); }

/* Focus and motion ----------------------------------------------------- */
a:focus-visible, .dlo-tile a:focus-visible { outline: 2px solid var(--wp--preset--color--accent); outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } }

/* Print ---------------------------------------------------------------
   The resume links here, so a printed page has to stay readable. Ink on
   white, hairlines in grey, and the mask fade off so the portrait is not
   half a head. */
@media print {
  @page { margin: 15mm; }
  html { color-scheme: light; }
  body { background: #fff; }

  /* The page is designed for light text on a dark surface, so every one of
     those colours has to be reversed rather than trusted. A blanket reset is
     blunt but it is the only version of this that cannot leave a paragraph
     printing in near-white. */
  .dlo-page, .dlo-page *, .wp-site-blocks { color: #000 !important; background: transparent !important; }

  /* Then hand the instrument labels back their quieter voice. */
  .dlo-hero__eyebrow, .dlo-hero__location, .dlo-tile__window,
  .dlo-case__note, .dlo-table caption, .dlo-table thead th,
  .dlo-tile__entity { color: #555 !important; }

  .dlo-page { max-width: none; padding: 0; }
  .dlo-page > section { margin-block-start: 1.5rem; break-inside: avoid; }
  h1, h2, h3 { break-after: avoid; }
  .dlo-tile a, .dlo-case { border-color: #999; }
  .dlo-tile a { border-radius: 0; }
  .dlo-tiles { gap: 0.5rem; }
  .dlo-case { border-inline-start-width: 2px; }
  .dlo-page > section > h2::before { background: #000; }
  .dlo-hero__portrait img { -webkit-mask-image: none; mask-image: none; max-width: 12rem; }
  .dlo-table, .dlo-tablewrap { overflow: visible; max-width: none; }
  .dlo-table { display: table; }
  .dlo-tablewrap { border: 0; }
  .dlo-table th, .dlo-table td { border-block-end-color: #bbb !important; }
  .dlo-table thead th { border-block-end-color: #666 !important; }
  .dlo-btn { padding-inline: 0; border: 0; font-weight: 400; }
  .wp-site-blocks > header, .wp-site-blocks > footer { display: none; }
  /* A printed link is useless without its target. */
  .dlo-contact a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 0.75rem; }
}

/* Contact form ------------------------------------------------------- */
/* Contact Form 7 supplies the markup, so these selectors target its output
   as well as the classes set in the form template. */
.dlo-contact__lead { max-inline-size: 46ch; }

.wpcf7 { max-inline-size: 38rem; margin-block-start: var(--wp--preset--spacing--40); }
.wpcf7 form > p { margin: 0; }

/* Two short fields sit side by side above 34rem. Halving the row count is
   what actually shortens the form; trimming gaps alone barely moved it. */
.dlo-form__pair { display: grid; gap: 0 var(--wp--preset--spacing--30); }
@media (min-width: 34rem) { .dlo-form__pair { grid-template-columns: 1fr 1fr; } }

/* The quiz and the submit button share a baseline row rather than stacking. */
.dlo-form__foot { display: flex; flex-wrap: wrap; align-items: end; gap: var(--wp--preset--spacing--30); }
.dlo-form__foot .dlo-form__actions { margin: 0 0 0.75rem !important; }

.dlo-form__row { display: grid; gap: 0.3rem; margin-block-end: 0.85rem !important; }
.dlo-form__row label { color: var(--wp--preset--color--bright); font-weight: 600; font-size: var(--wp--preset--font-size--small); }
.dlo-form__req { color: var(--wp--preset--color--muted); font-weight: 400; }

.wpcf7 input[type="text"],
.wpcf7 input[type="email"],
.wpcf7 textarea {
  /* Without this the padding and border add to inline-size: 100%, so a field
     renders 320px inside a 296px grid column and the paired fields overlap by
     8px instead of showing the 16px gap between them. */
  box-sizing: border-box;
  inline-size: 100%;
  padding: 0.5rem 0.7rem;
  font: inherit;
  color: var(--wp--preset--color--bright);
  background: var(--wp--preset--color--raised);
  border: 1px solid var(--wp--preset--color--line);
  border-radius: 0.5rem;
}
.wpcf7 textarea { resize: vertical; min-block-size: 5.5rem; }

.wpcf7 input:focus-visible,
.wpcf7 textarea:focus-visible {
  outline: 2px solid var(--wp--preset--color--accent);
  outline-offset: 2px;
  border-color: transparent;
}

/* The quiz field is the only spam defence on this form, so it must read as a
   real question rather than as decoration a bot can skip. CF7 emits its own
   <label> wrapping the question and the input, so the row heading above it is
   a span: two labels would say the question twice. */
.dlo-form__row--quiz { gap: 0.5rem; }
.dlo-form__quizlabel {
  color: var(--wp--preset--color--bright);
  font-weight: 600;
  font-size: var(--wp--preset--font-size--small);
}
.dlo-form__row--quiz label {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  font-weight: 400;
  color: var(--wp--preset--color--body);
}
.dlo-form__row--quiz input { max-inline-size: 7rem; }

.wpcf7 input[type="submit"] {
  padding: 0.7rem 1.4rem;
  font: inherit;
  font-weight: 600;
  color: var(--wp--preset--color--ink);
  background: var(--wp--preset--color--accent);
  border: 0;
  border-radius: 2rem;
  cursor: pointer;
}
.wpcf7 input[type="submit"]:hover { filter: brightness(1.08); }

/* CF7 status messages. Its defaults are light-theme and unreadable here. */
.wpcf7 form .wpcf7-response-output {
  margin: var(--wp--preset--spacing--30) 0 0;
  padding: 0.7rem 0.9rem;
  border-radius: 0.5rem;
  border-width: 1px;
  color: var(--wp--preset--color--bright);
}
.wpcf7 form.sent .wpcf7-response-output { border-color: var(--wp--preset--color--accent); }
.wpcf7 form.invalid .wpcf7-response-output,
.wpcf7 form.unaccepted .wpcf7-response-output,
.wpcf7 form.spam .wpcf7-response-output { border-color: #E8A33D; }

.wpcf7-not-valid-tip { color: #F0B357; font-size: var(--wp--preset--font-size--small); }
.wpcf7-spinner { background-color: var(--wp--preset--color--muted); }

.dlo-form__fallback { color: var(--wp--preset--color--body); }

@media print { .wpcf7 { display: none; } }

/* Blog ---------------------------------------------------------------- */
/* Posts are long-form prose, so the measure is tighter than the evidence
   pages, which run wide to hold tables. */
.dlo-post { max-width: 46rem; }
.dlo-post__head { margin-block-end: var(--wp--preset--spacing--50); }
.dlo-post__kicker { margin: 0 0 var(--wp--preset--spacing--30); }
.dlo-post__title { margin: 0 0 var(--wp--preset--spacing--30); }
.dlo-post__standfirst {
  font-size: var(--wp--preset--font-size--large);
  line-height: 1.5;
  color: var(--wp--preset--color--bright);
  margin: 0;
}
.dlo-post__byline { margin-block-start: var(--wp--preset--spacing--30); }

.dlo-post__body > * { margin-block: 0 var(--wp--preset--spacing--30); }
.dlo-post__body h2 {
  margin-block-start: var(--wp--preset--spacing--50);
  padding-block-start: var(--wp--preset--spacing--30);
  border-block-start: 1px solid var(--wp--preset--color--line);
}
.dlo-post__body h3 { margin-block-start: var(--wp--preset--spacing--40); }
.dlo-post__body ol, .dlo-post__body ul { padding-inline-start: 1.4rem; }
.dlo-post__body li { margin-block-end: 0.5rem; }
.dlo-post__body strong { color: var(--wp--preset--color--bright); }

/* A pull quote carries Google's own wording in the case study, so it needs to
   read as quoted evidence rather than as emphasis. */
.dlo-post__body blockquote {
  margin-inline: 0;
  padding: var(--wp--preset--spacing--30);
  border-inline-start: 3px solid var(--wp--preset--color--accent);
  background: var(--wp--preset--color--surface);
  color: var(--wp--preset--color--bright);
}
.dlo-post__body blockquote > *:last-child { margin-block-end: 0; }

.dlo-post__body code {
  font-family: var(--wp--preset--font-family--mono);
  font-size: 0.9em;
  padding: 0.1rem 0.3rem;
  border-radius: 0.25rem;
  background: var(--wp--preset--color--raised);
}

/* Markdown tables get the same treatment as the generated evidence tables. */
.dlo-post__body table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}
.dlo-post__body th, .dlo-post__body td {
  padding: 0.5rem 0.8rem;
  border-block-end: 1px solid var(--wp--preset--color--line);
  text-align: start;
}
.dlo-post__body thead th {
  font-family: var(--wp--preset--font-family--mono);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--wp--preset--color--muted);
}

.dlo-post__foot {
  margin-block-start: var(--wp--preset--spacing--50);
  padding-block-start: var(--wp--preset--spacing--30);
  border-block-start: 1px solid var(--wp--preset--color--line);
}

/* Index. Grouped by subject, not by date: a reverse-chronological feed
   advertises its own cadence, and a three-month-old top item reads as
   abandoned. */
.dlo-blog__head { margin-block-end: var(--wp--preset--spacing--50); }
.dlo-blog__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--wp--preset--spacing--40); }
.dlo-blog__item h3 { margin: 0 0 0.3rem; }
.dlo-blog__item p { margin: 0; max-width: 68ch; }

/* THUMBNAILS, 2026-09-02. Only illustrated entries get the two-column
   treatment: fallback cards were rejected, so an entry with no featured image
   keeps the full width rather than leaving a blank column beside it. A ragged
   list is the accepted consequence of not inventing pictures.

   The image is decorative HERE and only here: the same alt text is on the
   article's own copy of it, so repeating it in a list of links makes a screen
   reader read the description twice before the headline. The anchor is
   aria-hidden and out of the tab order for that reason; the headline beside
   it is the real link. */
@media (min-width: 40rem) {
  .dlo-blog__item--illustrated {
    display: grid;
    grid-template-columns: 12rem 1fr;
    gap: var(--wp--preset--spacing--30);
    align-items: start;
  }
}
.dlo-blog__thumb { display: block; }
.dlo-blog__thumb img {
  display: block;
  inline-size: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  border-radius: 0.5rem;
  border: 1px solid var(--wp--preset--color--line);
}
.dlo-blog__item--illustrated .dlo-blog__thumb { margin-block-end: var(--wp--preset--spacing--20); }
@media (min-width: 40rem) {
  .dlo-blog__item--illustrated .dlo-blog__thumb { margin-block-end: 0; }
}

/* The article's own lead image. Full measure, not the narrower diagram cap. */
.dlo-post__lead { margin: 0 0 var(--wp--preset--spacing--40); max-width: 46rem; }
.dlo-post__lead img {
  display: block; inline-size: 100%; block-size: auto;
  border-radius: 0.5rem; border: 1px solid var(--wp--preset--color--line);
}
/* The hub's Writing teaser reuses .dlo-blog__list, so it inherits the grid
   above. What it does not inherit is .dlo-blog__head's separation, because on
   the hub the list sits between a lead sentence and a "read all" link rather
   than under a page header. */
.dlo-writing .dlo-blog__list { margin-block: var(--wp--preset--spacing--40); }

.dlo-blog__date {
  font-family: var(--wp--preset--font-family--mono);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--wp--preset--color--muted);
  margin-block-start: 0.4rem !important;
}

/* Post images. Screenshots of real work, so they get a frame and a caption
   rather than floating in the measure. */
.dlo-post__body img {
  display: block;
  inline-size: 100%;
  block-size: auto;
  border: 1px solid var(--wp--preset--color--line);
  border-radius: 0.5rem;
  margin-block: var(--wp--preset--spacing--40) 0.6rem;
}
/* The paragraph directly after an image acts as its caption. */
.dlo-post__body img + p,
.dlo-post__body p:has(> img) + p {
  font-size: var(--wp--preset--font-size--small);
  color: var(--wp--preset--color--muted);
  max-width: 60ch;
}

/* Same marker treatment as the tools and tracks lists. */
.dlo-skills__block ul { padding-inline-start: 1.2rem; max-width: 68ch; }
.dlo-skills__block li { margin-block-end: 0.4rem; }
.dlo-skills__block li::marker { color: var(--wp--preset--color--accent); }

/* Certificate lightbox --------------------------------------------------- */
.dlo-visually-hidden {
  position: absolute; inline-size: 1px; block-size: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* The plate is a button now, so strip the UA button styling and keep the
   plate looking exactly as it did. */
.dlo-cred__zoom {
  position: relative;
  font: inherit;
  color: inherit;
  border: 0;
  cursor: zoom-in;
  inline-size: 100%;
  text-align: inherit;
}
.dlo-cred__zoomhint {
  position: absolute;
  inset-block-end: 0.3rem;
  inset-inline-end: 0.4rem;
  padding: 0.1rem 0.4rem;
  border-radius: 0.25rem;
  font-family: var(--wp--preset--font-family--mono);
  font-size: 0.65rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--wp--preset--color--ink);
  background: var(--wp--preset--color--accent);
  opacity: 0;
  transition: opacity 120ms ease;
}
.dlo-cred__zoom:hover .dlo-cred__zoomhint,
.dlo-cred__zoom:focus-visible .dlo-cred__zoomhint { opacity: 1; }
.dlo-cred__zoom:focus-visible { outline: 2px solid var(--wp--preset--color--accent); outline-offset: 3px; }

.dlo-lightbox {
  max-inline-size: min(92vw, 60rem);
  max-block-size: 92vh;
  padding: var(--wp--preset--spacing--30);
  border: 1px solid var(--wp--preset--color--line);
  border-radius: 0.75rem;
  background: var(--wp--preset--color--surface);
  color: var(--wp--preset--color--body);
}
.dlo-lightbox::backdrop { background: rgb(0 0 0 / 0.82); }
.dlo-lightbox__img {
  display: block;
  max-inline-size: 100%;
  max-block-size: 76vh;
  inline-size: auto;
  margin-inline: auto;
  border-radius: 0.4rem;
}
.dlo-lightbox__caption {
  margin: var(--wp--preset--spacing--20) 0 0;
  text-align: center;
  font-family: var(--wp--preset--font-family--mono);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--wp--preset--color--muted);
}
.dlo-lightbox__close { display: flex; justify-content: flex-end; margin-block-end: 0.4rem; }
.dlo-lightbox__close button {
  font: inherit;
  font-size: var(--wp--preset--font-size--small);
  padding: 0.3rem 0.8rem;
  border-radius: 2rem;
  border: 1px solid var(--wp--preset--color--line);
  background: transparent;
  color: var(--wp--preset--color--bright);
  cursor: pointer;
}
.dlo-lightbox__close button:hover { border-color: var(--wp--preset--color--accent); }

@media print { .dlo-cred__zoomhint { display: none; } }

/* FAQ answers may carry a bulleted list under the prose lead. Same accent
   marker as the tools, tracks and skills lists. */
.dlo-faq__list {
  margin: var(--wp--preset--spacing--20) 0 0;
  padding-inline-start: 1.2rem;
  max-width: 68ch;
}
.dlo-faq__list li { margin-block-end: 0.4rem; }
.dlo-faq__list li::marker { color: var(--wp--preset--color--accent); }
