/* ------------------------------
   Token previews
   ------------------------------
   The swatch each token type renders as, plus the row grid the swatches sit
   in. Its own stylesheet because two pages need it: the editor (/app) and
   the public Explore view, which is served to visitors with no account and
   must NOT pull in app.css — that sheet also lays out the app shell
   (`main { display: flex }`, `body { overflow: hidden }`), which would
   rearrange a plain document page.

   The JS counterpart is public/js/utilities/helpers/tokenPreview.mjs; the
   two are meant to move together. */

/* The heading row and the token rows are SEPARATE grid containers — they
   line up only because their templates resolve to the same widths. That is
   also why the last track is a fixed width rather than `auto`: the rows end
   in a remove button but the heading row ends in nothing, so an `auto`
   track resolves to ~25px in one and 0 in the other, and the difference is
   redistributed through the fr tracks — knocking every heading out of line
   with the field it labels. A fixed track resolves identically in both,
   whatever either one happens to contain, and lets the heading row simply
   omit that cell instead of carrying an empty <span> to hold its place. */
.token-list {
  --token-action-col: 1.75rem;
}

/* Nothing in a read-only list can be removed, so no row has the button that
   track exists to hold — and a reserved 28px of nothing reads as a column
   with something missing from it.

   Collapsed rather than the template being restated without its last track:
   the heading row and the token rows are separate grids that line up only
   because they resolve to the same widths (see above), and a second copy of
   those widths is a second thing to keep in step. One variable moves both. */
.token-list--readonly {
  --token-action-col: 0px;
  padding-inline: 0;
}

/* Name | Value | Preview | remove. Preview took over the column the
   per-token description used to occupy — a 28px sliver could not render a
   shadow or a dimension bar at a useful size. */
.column-headers,
.token-row {
  display: grid;
  grid-template-columns:
    minmax(8rem, 1fr) minmax(8rem, 1.2fr) minmax(6rem, 0.8fr) var(--token-action-col, 1.75rem);
  gap: var(--pf-space-3);
}

/* A basic group trades preview width for description width: the third
   column holds a sentence rather than a chip. */
.token-list--basic .column-headers,
.token-list--basic .token-row {
  grid-template-columns:
    minmax(7rem, 0.8fr) minmax(7rem, 0.9fr) minmax(12rem, 1.7fr) var(--token-action-col, 1.75rem);
}

/* A size group goes the other way: the preview takes most of the row and the
   two fields give it up.

   This is the one list type whose preview is PROPORTIONAL — the bar is drawn
   at the value's own width, so the column's width is the scale's resolution.
   At 0.8fr a 4px step and an 8px step land two pixels apart and a ramp reads
   as a smudge; the fields, meanwhile, are holding "spacing-lg" and "1.5rem"
   in space enough for a sentence.

   Name keeps more than Value because it needs more: names run to
   "spacing-xxl" while a length is rarely past six characters. Both keep a
   min that fits their realistic longest content, so the trade only happens
   where there is room to trade. */
.token-list--bar-preview .column-headers,
.token-list--bar-preview .token-row {
  grid-template-columns:
    minmax(7rem, 0.6fr) minmax(5.5rem, 0.4fr) minmax(10rem, 2fr) var(--token-action-col, 1.75rem);
}

/* A few previews (a type scale's actual text, at actual size) need more
   room than a column ever could, so the row becomes two lines: Name and
   Value on top, Preview spanning the full width below.

   The heading row takes the SAME three-track template — it just fills two
   of them — because it is a separate grid and would otherwise keep the
   four-track one, putting "Value" 85px away from the input it labels. */
.token-list--stacked-preview .column-headers,
.token-list--stacked-preview .token-row {
  grid-template-columns: minmax(8rem, 1fr) minmax(8rem, 1.2fr) var(--token-action-col, 1.75rem);
}

/* Areas are assigned positionally (nth-child), not by class, because the
   editor's row (input.token-name, input.token-value, span.swatch,
   button.remove-token) and Explore's read-only row (three plain spans)
   share this child order and nothing else — see ProjectPage.js's tokenRow()
   and explore.mjs's renderGroup(). */
.token-list--stacked-preview {
  .token-row {
    grid-template-areas:
      "name    value   remove"
      "preview preview preview";
    row-gap: var(--pf-space-4);
  }
  .rows {
    display: flex;
    flex-direction: column;
  }
}

.token-list--stacked-preview .token-row > :nth-child(1) { grid-area: name; }
.token-list--stacked-preview .token-row > :nth-child(2) { grid-area: value; }
.token-list--stacked-preview .token-row > :nth-child(3) { grid-area: preview; }
.token-list--stacked-preview .token-row > :nth-child(4) { grid-area: remove; }

/* The stacked chip's own appearance is defined further down, after the
   per-type rules it has to override — see "Stacked preview" at the end. */

.column-headers {
  font-size: var(--pf-text-xs);
  color: var(--pf-color-muted);
  letter-spacing: var(--pf-tracking-loose);
  padding-bottom: var(--pf-space-2);
  border-bottom: 1px solid var(--pf-color-border);
}

.token-row {
  align-items: center;
  padding: var(--pf-space-2) 0;
  border-bottom: 1px solid var(--pf-color-border);
  /* The containing block for the drag grip, which sits in the margin rather
     than in the grid — see .token-grip in app.css. */
  position: relative;
}

/* The preview chip. Its appearance is driven entirely by the GROUP's
   declared type — see tokenRow() — because the value alone cannot say
   which kind of token it is. Only [data-type="color"] gets the chequerboard
   (which reads as "transparent"); every other type styles itself, and the
   unrepresentable ones collapse rather than showing an empty color. */
.token-row .swatch {
  /* Fills the Preview column rather than sitting as a fixed square, so a
     dimension bar has room to show a real scale and a shadow has room to
     fall. Height matches the inputs beside it so rows stay aligned. */
  width: 100%;
  height: 35px;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  font-size: 11px;
  line-height: 1.25;
  color: var(--pf-color-muted);
}

/* The transparency grid, shared by every type painted with something that
   can be partly or wholly see-through. Themed like everything else — an
   icon written with currentColor adapts with it (see --pf-color-icon), and one
   with its own baked-in colors is the author's to get right.

   Held in a custom property as well as applied, because a color chip on a
   CARD needs the same grid as the fallback for a ground the author can
   replace — and one copy of the value is what keeps the two from drifting. */
.token-row .swatch[data-type="color"],
.token-row .swatch[data-type="gradient"],
.token-row .swatch[data-type="opacity"],
.token-row .swatch[data-type="image"],
.token-row .swatch[data-type="svg"] {
  --swatch-grid:
    repeating-conic-gradient(var(--pf-color-surface-sunken) 0% 25%, transparent 0% 50%) 50% / 12px 12px;
  border: 1px solid var(--pf-color-border-strong);
  background: var(--swatch-grid);
  border-radius: var(--pf-radius-sm);
}

/* The fill, edge to edge over the grid. A list row is one short strip and the
   color is the whole of it: there is no ground band here and no inner box, so
   none of the per-token settings apply on this layout — see the card rules
   further down, which are where the two-part presenter lives. */
.token-row .swatch[data-type="color"]::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--swatch-color, transparent);
}

/* A picture is set on the chip's own background-image, so it needs the grid
   to stay behind it rather than be replaced.

   `contain`, never `cover`: an asset preview exists to show you WHICH asset
   this is, and cropping can hide exactly the part that distinguishes it —
   a logo's wordmark, the subject of a photograph. Whatever the picture's
   proportions, it is fitted whole and the transparency grid shows through
   the remaining space, which reads honestly as "this is the shape of it".

   A gradient used to be in this list and no longer is: it paints ::after
   now, not the chip. The declarations here never applied to one anyway — a
   gradient has no intrinsic dimensions, so contain, cover and a position all
   resolve to the whole box. */
.token-row .swatch[data-type="image"],
.token-row .swatch[data-type="svg"] {
  background-repeat: no-repeat;
  background-position: center;
  /* contain unless the group says otherwise — see the "fit" setting in
     PREVIEW_SETTING_FIELDS. The property is only ever set on an image
     group's list, so gradients and SVG sets keep the default they have
     always had, and a gradient would be unaffected regardless: it has no
     intrinsic size, so contain and cover both resolve to the whole box. */
  background-size: var(--pf-preview-fit, contain);
}

/* A gradient chip is a ground and paint on it, the same two parts a color
   chip has on a card — and for the sharper version of the same reason. A ramp
   that ends in `transparent` is HALF a statement about what it will be laid
   over, and against the transparency grid alone that half is unreadable.

   The paint fills the chip unless the token says otherwise, which is how
   these have always been drawn and is right by default: the wider the box,
   the more of the ramp there is to see, and where it banks is most of what
   anyone is judging. --swatch-gradient rather than the chip's own
   background-image, so the ground underneath survives — see renderPreview.

   The border is transparent rather than absent, so it occupies its 1px
   whether or not it is set. Backgrounds paint under a border by default, so
   an unset edge is invisible; what it buys is that turning one on changes a
   color and not the geometry. */
.token-row .swatch[data-type="gradient"] {
  --edge-default: transparent;
  background: var(--stage-ground, var(--swatch-grid));
}

.token-row .swatch[data-type="gradient"]::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--swatch-gradient, transparent);
  border: var(--stage-border-width, 1px) solid
          var(--stage-border, var(--edge-default));
}

/* Boxed: the same inset, radius and default hairline the color and shadow
   cards use, because it is the same object — a card standing on a stage. A
   ramp written for a button or an overlay is a different claim from one
   written for a page, and this is where that claim gets made. */
.token-row .swatch[data-type="gradient"][data-boxed]::after {
  inset: 18%;
  border-radius: var(--pf-radius-sm);
}

.token-row .swatch[data-type="gradient"][data-boxed] {
  --edge-default: var(--pf-color-border-strong);
}

/* Opacity is meaningless in isolation: a solid block of a known color is
   faded over the grid, so the value reads as how much shows through. */
.token-row .swatch__opacity {
  position: absolute;
  inset: 0;
  /* The project's preview color, like every other sample the app draws — an
     opacity ramp is only legible against something, and that something ought
     to be the system's own color rather than a fixed one.

     Ink when nothing is set, not the brand green the SHAPE previews fall back
     to. Opacity is read as how much shows THROUGH, so the block wants the
     most opaque thing available; a mid-tone brand color at 20% and at 30%
     are much harder to tell apart than ink is. */
  background: var(--pf-preview-example, var(--pf-color-ink));
}

/* --pf-preview-example is the color the SAMPLE is drawn in — the square a
   radius is shown on, the bar a spacing is shown as, the two halves of the
   filter chip. It is set per project from Project settings and falls back to
   the brand, which is what every project used before it could be chosen.

   Its own property rather than --pf-color-primary, which it defaults to: that
   token is also every button, focus ring and selection in the app, so a
   project choosing a pale example color would have taken the interface with
   it. And distinct from --pf-preview-color, which is the color TEXT samples
   are drawn in and is chosen per group. */

/* The shared body for types that describe a SHAPE rather than a fill —
   radius, border width, mask, transform, clip path. A square, because a
   corner radius or a clipped edge is only legible on one. */
.token-row .swatch__box {
  width: 35px;
  height: 35px;
  background: var(--pf-preview-example, var(--pf-color-primary));
  mask-repeat: no-repeat;
  mask-position: center;
  mask-size: contain;
}

/* A mask is a stencil, so what fills it IS the preview. Free CSS rather than
   a color — see PREVIEW_SETTING_FIELDS — because a mask is most often checked
   against a photograph, and it falls back through the project's sample color
   so a group that has said nothing still shows a solid shape. */
.token-row .swatch[data-type="imageMask"] .swatch__box {
  background: var(--pf-preview-mask-bg,
      var(--pf-preview-example, var(--pf-color-primary)));
  background-size: cover;
  background-position: center;
}

/* Border width is the one shape type where the border IS the value, so the
   fill is dropped and only the outline carries meaning. */
.token-row .swatch__box--outline {
  background: none;
  border: 0 solid var(--pf-preview-example, var(--pf-color-primary));
}

/* Shadow needs a solid body to cast from, and a GROUND to cast onto. The
   ground used to be whatever the page happened to be, which meant a dark
   shadow on the dark theme and a light glow on the light one both fell on
   their own color and simply were not there. The chip is now a stage of a
   known tone — see --pf-shadow-stage — and the body is a known tone on top
   of it, so the only unknown left in the picture is the value.

   Two consequences follow from the chip becoming the ground.

   It clips (it inherits `overflow: hidden` from .swatch — the rule that used
   to set `visible` here is gone). A shadow escaping the stage would land back
   on the page, which is the exact problem the stage exists to fix; leaving
   the frame is also how every design tool draws elevation, so the clip reads
   as intentional rather than as damage.

   And the chip no longer casts. renderPreview sets --swatch-shadow rather
   than the chip's own box-shadow, which the body picks up below. An element
   cannot be both the ground and the thing standing on it: while the chip
   carried the value itself AND ::after inherited it, every shadow was drawn
   twice — once correctly around the body, and once as a halo around the whole
   chip, which was unmissable on a spread-only value like a focus ring. */
.token-row .swatch[data-type="boxShadow"],
.token-row .swatch[data-type="textShadow"] {
  /* Indirected through local properties so the dark stage below can be one
     three-line block rather than a restatement of every rule that uses them. */
  --stage: var(--pf-shadow-stage);
  --stage-body: var(--pf-shadow-stage-body);
  --stage-edge: var(--pf-shadow-stage-edge);
  --stage-ink: var(--pf-shadow-stage-ink);
  --stage-ink-strong: var(--pf-shadow-stage-ink-strong);
  background: var(--stage);
  border-radius: var(--pf-radius-sm);
}

/* Chosen per token, not per theme — see the token block for why. Set by the
   preview control on the row, and carried to the published page by
   display_settings, so a reader sees the stage the author picked. */
.token-row .swatch[data-type="boxShadow"][data-stage="dark"],
.token-row .swatch[data-type="textShadow"][data-stage="dark"] {
  --stage: var(--pf-shadow-stage-dark);
  --stage-body: var(--pf-shadow-stage-dark-body);
  --stage-edge: var(--pf-shadow-stage-dark-edge);
  --stage-ink: var(--pf-shadow-stage-dark-ink);
  --stage-ink-strong: var(--pf-shadow-stage-dark-ink-strong);
}

/* The body. Inset well away from the edges on the short axis, because that
   room IS the preview: a shadow with nowhere to fall is a shadow you cannot
   size. boxShadow is a CARD type (see CARD_PREVIEW_TYPES), so the rule below
   is what actually renders — this one is the fallback for a list row, and it
   keeps a narrow, centred body rather than a full-width bar for the same
   reason. */
.token-row .swatch[data-type="boxShadow"]::after {
  content: "";
  position: absolute;
  inset: 7px 28%;
  border-radius: var(--pf-radius-sm);
  background: var(--stage-body);
  /* --stage-border where a token set one, the stage's hairline otherwise.
     Its own property rather than overriding --stage-edge directly: that
     variable also tints the settings gear's hover, and a border color
     chosen for a card has no business changing a button. */
  border: var(--stage-border-width, 1px) solid var(--stage-border, var(--stage-edge));
  box-shadow: var(--swatch-shadow, none);
}

/* Text shadow's body is the SAMPLE, not a card. The chip is still the
   ground — same stage tokens as boxShadow — so a dark drop-shadow and a
   light glow can each be judged. Overflow clips for the same reason the
   box stage does: a shadow that lands back on the page is back on a
   color the author did not pick.

   Padding is the room the shadow falls into. --stage-ink-strong is the
   default face, so clearing a color override drops back to a pairing
   that still reads rather than to the theme's ink (which would vanish
   on the dark stage). */
.token-row .swatch[data-type="textShadow"] {
  color: var(--stage-ink-strong, var(--pf-color-ink));
  padding: var(--pf-space-3);
  overflow: hidden;
}

/* Transform can push the box outside the chip — that displacement is the
   value, so it must not be clipped away. */
.token-row .swatch[data-type="transform"] {
  overflow: visible;
}

/* A filter needs color and an edge to act on, or blur, saturate and
   contrast are indistinguishable. */
/* A filter needs something with color AND edges to act on, or blur,
   saturate and contrast all look identical. A checkerboard gives both: the
   squares are hard edges for blur and contrast to soften and sharpen, and
   they are drawn in the project's own preview color so saturate, hue-rotate
   and sepia have a hue to move.

   That is why this is a two-TONE board rather than the grey transparency
   grid the color chips use. Grey would show blur beautifully and every
   color filter not at all. */
.token-row .swatch__filter {
  width: 26px;
  height: 26px;
  background: var(--pf-preview-filter-bg,
      repeating-conic-gradient(var(--pf-preview-example, var(--pf-color-primary)) 0% 25%,
                               var(--pf-color-surface) 0% 50%) 50% / 24px 24px);
}

/* On a card the demo fills it. The 26px square above is what a 35px list row
   could hold; a filter is judged on area — a blur radius is in pixels, and a
   hard edge between two colors is what makes contrast and saturate legible,
   so both want as much of the card as they can get. */
.token-list--cards .token-row .swatch__filter {
  width: 100%;
  height: auto;
  align-self: stretch;
}

/* Transform's demo is the same board with its own background variable —
   see the note on .swatch__filter for why it is two-tone rather than grey. */
.token-row .swatch__transform {
  width: 26px;
  height: 26px;
  background: var(--pf-preview-transform-bg,
      repeating-conic-gradient(var(--pf-preview-example, var(--pf-color-primary)) 0% 25%,
                               var(--pf-color-surface) 0% 50%) 50% / 24px 24px);
}

/* Inset rather than full-bleed, which is the one place transform parts from
   filter: a filter acts in place, so it can fill the card, while a transform
   MOVES — and a translate or a rotation you cannot see leave the card is not
   a preview of anything. Sizing it to 60% leaves room on every side for the
   displacement to happen in. */
.token-list--cards .token-row .swatch__transform {
  width: 60%;
  height: auto;
  aspect-ratio: 1;
}

/* Types that show a letterform. */
.token-row .swatch[data-type="fontFamily"],
.token-row .swatch[data-type="fontSize"],
.token-row .swatch[data-type="fontWeight"],
.token-row .swatch[data-type="lineHeight"],
.token-row .swatch[data-type="letterSpacing"],
.token-row .swatch[data-type="textShadow"] {
  font-size: 15px;
}

.token-row .swatch[data-type="fontFamily"],
.token-row .swatch[data-type="fontSize"],
.token-row .swatch[data-type="fontWeight"],
.token-row .swatch[data-type="lineHeight"],
.token-row .swatch[data-type="letterSpacing"] {
  color: var(--pf-color-ink);
}

/* The sample text itself, one line, truncated rather than wrapped — the
   chip has a fixed height, and wrapped text would just get clipped mid-line
   with no indication anything was cut off. min-width: 0 overrides flexbox's
   default of never shrinking a child below its content size, which is what
   would otherwise stop this ellipsis from ever engaging. */
.token-row .swatch__sample {
  /* The group's preview font, set on the list by initGroupSettings, or the
     app's own where a group has no such setting. A fontFamily token sets its
     face inline on this same element, and an inline style beats this — which
     is what stops a group-wide override from contradicting the very thing
     those rows exist to show. */
  font-family: var(--pf-preview-font, inherit);
  /* --stage-fg is a text-shadow token's own face. Then the group's preview
     color. Then whatever the chip itself is: stage ink on a shadow, theme
     ink everywhere else. A token that states its own weight inline beats
     --pf-preview-weight, which is what stops a group-wide setting
     contradicting the very thing its rows exist to show. */
  color: var(--stage-fg, var(--pf-preview-color, inherit));
  font-weight: var(--pf-preview-weight, inherit);
  /* And the size, for the same reason and with the same escape: a fontSize
     token sets its own inline and beats this. It matters most for leading,
     which is not a number you can judge in the abstract — 1.4 is comfortable
     at 16px and cramped at 32px, so a group previewing a display scale needs
     to say so. */
  font-size: var(--pf-preview-size, inherit);
  display: block;
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* Line height is the one text preview that must WRAP: the value is the space
   between lines, so a single truncated line shows none of it. Everything
   else stays on one line and ellipses, because for a face, a size or a
   tracking the second line adds nothing but height. */
.token-row .swatch__sample--wrap {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  /* A reading measure, which does two jobs: it is the width leading is
     actually judged at, and it guarantees the sample wraps on a wide screen
     where one sentence would otherwise sit on a single line and show no
     leading at all.

     `ch` rather than a fixed length so the measure follows the type: at
     70ch the line holds the same number of characters whatever face and
     size the group is previewing at, which is what makes two leading
     values comparable. */
  max-width: 70ch;
  /* Justified with hyphenation, because leading is read off the RHYTHM of a
     block and a ragged edge adds a second, louder rhythm on top of it. This
     is the one place in the app that justifies text: everywhere else the
     ragged edge is easier to read, and here the block is a specimen rather
     than something anyone is reading for content. */
  text-align: justify;
  hyphens: auto;
}

/* Font size sets its own, capped in JS so a display size cannot outgrow the
   row. Bottom-aligned so the baselines line up down the column and the
   scale reads as a ramp rather than a row of centred blobs. */
.token-row .swatch[data-type="fontSize"] {
  justify-content: start;
}

/* A left-aligned bar whose width IS the value, so a size scale reads as a
   ramp down the column. */
.token-row .swatch[data-type="size"] {
  justify-content: flex-start;
}

.token-row .swatch__bar {
  height: 100%;
  min-width: 1px;
  background: var(--pf-preview-example, var(--pf-color-primary));
  border-radius: var(--pf-radius-sm);
}

/* ------------------------------
   Stacked preview
   ------------------------------
   Deliberately last in this file. These rules override per-type ones above
   (notably [data-type="fontSize"]'s align-items) at EQUAL specificity —
   three classes each — so the cascade resolves this by source order, and
   moving this block earlier would silently stop it winning. */

/* Flipped to a column so the caption and the sample stack, and so the space
   between them is a real `gap` rather than whatever happens to be left over.
   As an absolutely positioned ::before the caption took no space at all, so
   that space shrank as the sample grew — roomy under a 12px sample, nearly
   touching a capped 40px one. A gap is the same at every size.

   align-items is stretch, not the flex-end inherited from the fontSize rule
   above: in a column that property controls the HORIZONTAL axis, where
   flex-end would right-align the text and shrink-wrap the sample, breaking
   .swatch__sample's ellipsis (it needs full width to overflow against). */
.token-list--stacked-preview .token-row .swatch {
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  gap: var(--pf-space-2);
  /* Height follows the sample instead of clipping it — a type scale's rows
     SHOULD grow with the scale — with padding for breathing room. */
  height: auto;
  padding-block: var(--pf-space-1);
  /* And nothing here clips. The base chip hides its overflow because it is a
     fixed 35px box that has to contain whatever it is given; this one sizes
     to its content, so the only thing the clip could still catch is the
     settings gear, which sits in the row gap ABOVE the specimen on purpose.
     It did catch it — the gear was positioned correctly, reported the right
     opacity, and was invisible. Every sample that needs to truncate does its
     own clipping on .swatch__sample. */
  overflow: visible;
}

/* A stacked preview carries no "Preview" caption. .column-headers has no
   column left to label it with — the preview no longer sits beside Name and
   Value, and the JS omits that cell entirely for these types — and a caption
   over the specimen was tried and dropped: the sample is self-evidently the
   sample, and a label on it was one more piece of furniture on the row whose
   whole point is the type. */

/* The point of the whole stacked treatment: at 40px a typeface, a weight, a
   tracking value or a text shadow is actually judgeable, where the 15px of
   the narrow column left them as a squint. Listed by type rather than set
   on the block above, so adding a non-textual type to WIDE_PREVIEW_TYPES
   later does not silently inherit a text size.

   fontSize is here for the caption/gap it shares, but overrides this per
   row from its own value — uncapped, so a 4.5rem step actually renders at
   4.5rem and the row grows with it. This 40px is only its starting point,
   for a token whose value the browser refuses. */
.token-list--stacked-preview .token-row .swatch[data-type="fontFamily"],
.token-list--stacked-preview .token-row .swatch[data-type="fontSize"],
.token-list--stacked-preview .token-row .swatch[data-type="fontWeight"],
.token-list--stacked-preview .token-row .swatch[data-type="letterSpacing"],
.token-list--stacked-preview .token-row .swatch[data-type="textShadow"] {
  font-size: 40px;
}

/* Stacked chips size to their content and unclip so a settings gear can
   sit in the row gap. A text-shadow stage cannot: the clip IS the
   preview, and the padding restated here beats the stacked chip's
   padding-block: space-1, which would leave the shadow nowhere to fall. */
.token-list--stacked-preview .token-row .swatch[data-type="textShadow"] {
  overflow: hidden;
  padding: var(--pf-space-3);
}

/* Hierarchy inside a stacked row.
   ------------------------------
   Three things were competing at full contrast: the name, the value, and the
   sample — all --pf-color-ink, each in its own bordered box. Nothing said which
   to read first, and on a type scale the answer is obviously the sample:
   that IS the token, and the fields are how you refer to it.

   Figure and ground. The sample keeps ink and its own size, so it is the
   only element at full strength and reads as the figure. The fields drop to
   --pf-color-muted and lose their border, receding to ground — and come back to
   full contrast the moment you hover or focus them, so nothing is lost, only
   deferred. That is the same treatment .project-name and .group-name already
   use for a heading that happens to be editable; this extends it to a row
   whose real subject is underneath the fields rather than in them.

   Similarity does the rest: name and value are left identical to each other,
   so they read as one pair of metadata rather than as two separate controls
   arguing for attention. */
.token-list--stacked-preview .token-row input.token-name,
.token-list--stacked-preview .token-row input.token-value {
  color: var(--pf-color-muted);
  transition: color var(--pf-duration-fast) var(--pf-ease-out),
              border-color var(--pf-duration-fast) var(--pf-ease-out);
}

.token-list--stacked-preview .token-row input.token-name:hover,
.token-list--stacked-preview .token-row input.token-value:hover,
.token-list--stacked-preview .token-row input.token-name:focus,
.token-list--stacked-preview .token-row input.token-value:focus {
  border-color: var(--pf-color-border-strong);
  color: var(--pf-color-ink);
}

/* The column headers label fields that are now quiet at rest; matching them
   to the fields keeps the pair reading as one band of metadata rather than
   leaving a heading louder than the thing it heads. */
.token-list--stacked-preview .column-headers {
  color: var(--pf-color-faint);
}

/* ------------------------------
   Card preview
   ------------------------------
   Image groups (CARD_PREVIEW_TYPES) drop the row/column model entirely: a
   list gives its preview one short, wide cell, which is fine for a color
   chip and useless for a picture. Each token becomes a card — picture on
   top, then the name with its remove button beside it (the same
   name-then-action order the list rows use), then the URL underneath.

   Like the stacked variant, this is defined after the per-type rules it
   overrides, and assigns areas positionally because the editor's row
   (two inputs, a swatch, a button) and Explore's read-only row (three
   spans plus an empty one) share only their child ORDER. */

/* No column headings — each card carries its own labels instead (below). */
.token-list--cards .column-headers {
  display: none;
}

.token-list--cards .rows {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
  gap: var(--pf-space-5);
}

/* One column: picture, then each field under its own label. The remove
   button leaves the flow entirely — it shares the preview cell and appears
   over the image on hover (see below), which keeps the text fields full
   width and stops a destructive control sitting permanently in the middle
   of the card. */
.token-list--cards .token-row {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas:
    "preview"
    "name-label"
    "name"
    "value-label"
    "value";
  row-gap: var(--pf-space-1);
  /* Bordered but unfilled: the outline groups the picture with its two
     fields, while the page shows through so the cards do not read as a
     wall of panels. `border` is set outright rather than left to the base
     rule, which only draws a bottom edge — that was a list divider, and on
     a card it would read as a stray rule across the middle. */
  padding: var(--pf-space-3);
  border: 1px solid var(--pf-color-border);
  border-radius: var(--pf-radius-sm);
  background: none;
}

.token-list--cards .token-row > :nth-child(1) { grid-area: name; }
.token-list--cards .token-row > :nth-child(2) { grid-area: value; }
.token-list--cards .token-row > :nth-child(3) { grid-area: preview; }

/* The two field labels, replacing the column headings this layout has no
   room for. Pseudo-elements rather than real nodes on purpose: :nth-child
   ignores them, so the positional area assignments above keep working and
   neither renderer needs to know these exist. Same 9px mono treatment as
   the stacked "Preview" caption — present when looked for, quiet
   otherwise. */
.token-list--cards .token-row::before,
.token-list--cards .token-row::after {
  margin-top: var(--pf-space-1);
  font-size: 9px;
  line-height: 1;
  color: var(--pf-color-muted);
  letter-spacing: var(--pf-tracking-loose);
}

.token-list--cards .token-row::before {
  content: "Name";
  grid-area: name-label;
}

.token-list--cards .token-row::after {
  content: "Value";
  grid-area: value-label;
}

/* The picture itself: a real aspect ratio rather than the 35px strip a row
   would allow, which is the entire point of this layout. Unbordered — the
   transparency grid behind it already marks the box, and a contained image
   sits inside that. */
.token-list--cards .token-row .swatch {
  height: auto;
  aspect-ratio: 4 / 3;
  border: none;
}

/* Types that draw a SHAPE rather than painting the chip — clipPath — carry
   a fixed 35px box, sized for a list row. Left alone it would be a speck
   adrift in a card ten times its width, which defeats the point of moving
   these here. Stretched to the card's height and held square, so the
   silhouette is the thing you actually see. */
.token-list--cards .token-row .swatch__box {
  width: auto;
  height: auto;
  align-self: stretch;
  aspect-ratio: 1;
}

/* A mask fills its card instead of sitting square in the middle of one.
   Square is right for a radius or a border width, whose value lives at the
   corners — a mask has no corners of its own, it takes the shape of whatever
   it is applied to, and in a real layout that is almost never a square.

   `cover`, and the choice is narrower than it looks. Widening the box alone
   changes nothing: at the inherited `contain` a square-artboard mask still
   draws itself square inside a wider box. Nor does `100% 100%` help — an SVG
   used as a mask image honours its own preserveAspectRatio, so asking it to
   stretch is simply declined and it letterboxes exactly as `contain` does.
   Only `cover` fills, by scaling until the box is covered and cropping what
   hangs over. That crop is the real cost of this rule: a tall mask loses its
   top and bottom here, which is the trade for seeing the shape at the width
   it is actually used at. */
.token-list--cards .token-row .swatch[data-type="imageMask"] .swatch__box {
  width: 100%;
  aspect-ratio: auto;
  mask-size: cover;
}

/* ---- values withheld ----

   A published system shows what its tokens look like, not what they are: the
   value column goes and Duplicate is the way to the numbers.

   The cell is hidden rather than removed, and that matters — the card and
   stacked layouts assign their grid areas by :nth-child, so a row with one
   fewer child would slide every preview into the cell before it. display:none
   leaves the count alone.

   Worth being clear about what this is: the previews are drawn in the browser
   from the token document, so the values still reach the page and can be read
   in its source. This withholds them from the interface, not from a
   determined reader. */
.token-list--no-values .explore-cell--value {
  display: none;
}

/* List layouts only. Cards assign areas by name on a single column; if this
   three-track template reached them they would collapse into a left-aligned
   row and lose the padding that makes them cards. */
.token-list--no-values:not(.token-list--cards) .column-headers,
.token-list--no-values:not(.token-list--cards) .token-row {
  grid-template-columns:
    minmax(8rem, 1fr) minmax(6rem, 0.8fr) var(--token-action-col, 1.75rem);
}

.token-list--no-values.token-list--basic .column-headers,
.token-list--no-values.token-list--basic .token-row {
  grid-template-columns:
    minmax(7rem, 0.8fr) minmax(12rem, 1.7fr) var(--token-action-col, 1.75rem);
}

/* The stacked layout names its areas, so the value one simply goes unused —
   but its COLUMN would still hold width, leaving a gap where the values were. */
.token-list--no-values.token-list--stacked-preview .token-row {
  grid-template-columns: minmax(8rem, 1fr) var(--token-action-col, 1.75rem);
  grid-template-areas:
    "name    remove"
    "preview preview";
}

/* The card layout captions its two fields with pseudo-elements; the Value
   one would otherwise label an empty row. Drop that caption and the value
   tracks, so a published card is the editor's card without the value field. */
.token-list--no-values.token-list--cards .token-row {
  grid-template-areas:
    "preview"
    "name-label"
    "name";
}

.token-list--no-values.token-list--cards .token-row::after {
  content: none;
}

/* ---- the small card ----

   The same card, sized for a shape rather than a picture. Two changes and no
   more: a tighter track, and a square preview.

   Square because a corner radius is read at its corners, and a 4:3 box gives
   you a different curve at the top than at the side — which invites a
   comparison that is an artefact of the box, not of the token. A border width
   has the same problem: on a rectangle the top and side edges of one value
   look like two values.

   Tighter because these need much less room than a photograph. At the full
   17rem a five-step radius scale is spread wider than the eye can compare in
   one go, which is the whole job of a scale. */
.token-list--cards-small .rows {
  grid-template-columns: repeat(auto-fill, minmax(10.5rem, 1fr));
  gap: var(--pf-space-4);
}

.token-list--cards-small .token-row .swatch {
  aspect-ratio: 1;
}


/* What a picture is seen AGAINST, and how it is composited onto it. Neither
   touches the asset: a transparent logo is invisible until it has a ground,
   and a blend mode is how you check one survives being laid over a brand
   color before shipping it.

   background-color and background-blend-mode rather than a wrapper and
   mix-blend-mode: the image and the color are two background layers of ONE
   element, so they blend with each other. Mixing the element instead would
   blend the whole chip into the editor's page.

   Set here rather than inline because renderPreview clears style.cssText on
   every repaint — a custom property on the list survives that, an inline
   declaration would not. */
.token-row .swatch[data-type="image"],
.token-row .swatch[data-type="imageMask"],
.token-row .swatch[data-type="svg"] {
  background-color: var(--pf-preview-bg, transparent);
  background-blend-mode: var(--pf-preview-blend, normal);
}


/* A shadow on a card. The body is inset much further than the 4px a list row
   allowed, because the space around it IS the preview: a 24px blur with 4px
   of room to fall into looks identical to an 8px one, which makes an
   elevation scale unreadable at exactly the point it matters.

   18% rather than the 22% this started at. The band is still ~45px on the
   short axis of a card, which is more room than any shadow in a sane
   elevation scale needs to finish falling, and the body is the thing being
   looked at — a stage with more ground than card in it reads as a diagram of
   a shadow rather than as one. Shared with the color chip below, which is
   built the same way and has to stay the same size. */
.token-list--cards .token-row .swatch[data-type="boxShadow"]::after {
  inset: 18%;
}

/* A color on a card, built as the same two-part presenter and inset to match
   it: the chip is the GROUND and ::after is the box standing on it. On a list
   row the color simply fills the strip, which states the value and nothing
   else. Here the ground is the second half of the picture, and it has to be
   separate from the box or there is nowhere for a translucent color to be
   translucent AGAINST — the whole reason a scrim or a glass fill is worth
   previewing at all.

   The ground REPLACES the grid rather than sitting under it: the grid is half
   transparent, so a color painted behind it would tint every other square and
   read as a pattern nobody chose. Unset, the grid is still what says
   "see-through", which is why it is the fallback rather than a rule of its
   own. */
.token-list--cards .token-row .swatch[data-type="color"] {
  background: var(--stage-ground, var(--swatch-grid));
}

/* The box. Same inset, same radius and the same border pair as the shadow's
   card above, because it is the same object seen twice — a card standing on a
   stage — and two card types that are built alike should look alike.

   It is also the only element that can carry the backdrop filter:
   backdrop-filter acts on what is painted BEHIND the element, so a filter on
   the chip itself would reach past its own ground to the page. Here it sits
   one layer above the ground and filters exactly that. */
.token-list--cards .token-row .swatch[data-type="color"]::after {
  inset: 18%;
  border-radius: var(--pf-radius-sm);
  border: var(--stage-border-width, 1px) solid
          var(--stage-border, var(--pf-color-border-strong));
  backdrop-filter: var(--stage-backdrop, none);
}

/* Over the image, top-right, on hover or keyboard focus. Opacity rather
   than display/visibility so it stays focusable and reachable by tab —
   :focus-within is what reveals it for anyone not using a mouse. */
.token-list--cards .token-row .remove-token {
  grid-area: preview;
  justify-self: end;
  align-self: start;
  /* Inset by the same step the settings gear uses, and shaped like it: on a
     card these two are a PAIR of corner controls, and a flush 27x28 button
     beside an inset 24x24 one reads as two unrelated things that happened to
     land near each other. Same size, same top edge, opposite corners. */
  margin: var(--pf-space-1);
  display: grid;
  place-items: center;
  width: 1.5rem;
  aspect-ratio: 1;
  padding: 0;
  /* Shares a grid cell with the picture, so it needs to be explicitly
     above it — grid items in the same cell stack in DOM order, and the
     swatch comes first. */
  position: relative;
  z-index: 1;
  /* Its own surface: the button sits on an arbitrary image and would be
     unreadable against a busy or same-toned one. */
  background: var(--pf-color-surface);
  border: 1px solid var(--pf-color-border-strong);
  border-radius: var(--pf-radius-sm);
  opacity: 0;
  transition: opacity var(--pf-duration-fast) var(--pf-ease-out),
              color var(--pf-duration-fast) var(--pf-ease-out),
              border-color var(--pf-duration-fast) var(--pf-ease-out),
              background-color var(--pf-duration-fast) var(--pf-ease-out);
}

.token-list--cards .token-row:hover .remove-token,
.token-list--cards .token-row:focus-within .remove-token {
  opacity: 1;
}

/* The same danger state a list row's remove button shows.

   It has to be restated here at all because the rule above gives this button
   an opaque surface, and `.token-list--cards .token-row .remove-token` and
   `.token-row .remove-token:hover` are both three classes — a tie, and this
   sheet loads after app.css, so the surface was quietly winning and the
   button only ever turned red in the text.

   --pf-color-danger-soft is translucent by design: over a list row it tints
   whatever the row sits on, which is the page. This button sits on an
   arbitrary picture, so the tint is composited over an opaque base of its
   own instead — and that base is --pf-color-bg, the same thing the list rows
   resolve against, so the two land on exactly the same color rather than
   merely a similar one. Opaque either way, which is the whole reason this
   button has a background at all.

   :focus-visible as well as :hover, because focus is what reveals the button
   for anyone not using a mouse; without it they would see it appear and
   never see it arm. */
.token-list--cards .token-row .remove-token:hover,
.token-list--cards .token-row .remove-token:focus-visible {
  background:
    linear-gradient(var(--pf-color-danger-soft), var(--pf-color-danger-soft)),
    var(--pf-color-bg);
  border-color: var(--pf-color-danger);
  color: var(--pf-color-danger);
}

/* Hover-to-reveal has no equivalent on a touch screen, where the button
   would be invisible yet still tappable — a destructive control nobody can
   see but anyone can hit. Where there is no hover, it is simply always
   shown. */
@media (hover: none) {
  .token-list--cards .token-row .remove-token {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .token-list--cards .token-row .remove-token {
    transition: none;
  }
}

/* A row holding a value the project cannot save — today only an SVG over the
   size limit. The mark is on the row rather than on the field because the
   reason is about the whole token, and it is a left edge rather than a red
   input: the markup in there is not malformed, there is simply too much of
   it, and coloring the field would say the wrong thing.

   Last in the file and carrying a third class on purpose. The card layout
   gives every row a full border (`.token-list--cards .token-row`), which is
   two classes and would otherwise tie with a bare `.token-row--invalid` and
   win on source order — so the marker would silently do nothing in exactly
   the layouts SVG groups use. */
.token-list .token-row.token-row--invalid {
  border-left: 3px solid var(--pf-color-danger);
}
