:root
{
    --ui-color-surface: Canvas;
    --ui-color-text: CanvasText;
    --ui-color-accent: AccentColor;
    --ui-color-danger: light-dark(#b3261e, #f2b8b5);
    --ui-color-muted: color-mix(in srgb, CanvasText 62%, Canvas);
    --ui-color-border: color-mix(in srgb, CanvasText 16%, Canvas);
    --ui-color-surface-raised: color-mix(in srgb, CanvasText 5%, Canvas);
    --ui-space-1: 0.5rem;
    --ui-space-2: 1rem;
    --ui-space-3: 1.5rem;
    --ui-space-4: 2.5rem;
    --ui-space-5: 4rem;
    --ui-space-6: 6rem;
    --ui-radius-1: 0.5rem;
    --ui-font-body: system-ui, sans-serif;
    --ui-font-heading: var(--ui-font-body);
    --ui-font-mono: ui-monospace, monospace;
    --ui-measure: 68ch;
    --ui-container-size: 72rem;
    --ui-container-gutter: var(--ui-space-2);
    --ui-duration: 150ms;
    --ui-ease: ease-out;
    --ui-sheet-inline-size: min(32rem, 100%);
    color-scheme: light dark;
}

/* A stored manual theme choice sets data-theme on the root element; without
   it the page follows prefers-color-scheme. */
:root[data-theme="light"]
{
    color-scheme: light;
}

:root[data-theme="dark"]
{
    color-scheme: dark;
}

/* Breakpoints cannot be custom properties inside a media query. All
   components and all domains use these two widths:
   compact: below 40rem, wide: from 64rem. */

/* ui-button: a native button with an optional prefix, the label and an
   optional suffix. The root is the ui-button element or any element with the
   ui-button attribute, so handwritten HTML gets the same rules as composed
   output. The button is the root's button child; the label is its span
   without a part name, prefix and suffix are the spans named by part. Every
   selector walks from the root through child combinators, so a button inside
   slot content is never matched. States are pseudo-classes of the button
   itself. Sizes and colours come from --ui-button-* custom properties. They
   are read where they are used, so a caller may set them on any ancestor.

   Variants (host attribute variant): filled (default), outlined and text.
   Every variant keeps a 1px border, transparent where none shows, so all
   variants have the same size and a forced-colours mode can paint it. */

/* A button does not stretch as a block, even with display: block; as the
   host's flex item it grows to a width the caller gives the host. */
:is(ui-button, [ui-button])
{
    display: inline-flex;
}

/* The block padding plus the 1.25 line height and the border give the height
   of a ui-textfield with the default padding, so both align in a row. */
:is(ui-button, [ui-button]) > :is(button, a)
{
    align-items: center;
    background: var(--ui-button-background, var(--ui-color-accent));
    box-sizing: border-box;
    text-align: center;
    text-decoration: none;
    border: 1px solid var(--ui-button-border-color, transparent);
    border-radius: var(--ui-button-radius, var(--ui-radius-1));
    color: var(--ui-button-color, var(--ui-color-surface));
    display: flex;
    flex: 1 1 auto;
    font: inherit;
    font-size: var(--ui-button-font-size, inherit);
    font-weight: var(--ui-button-font-weight, inherit);
    gap: var(--ui-button-gap, var(--ui-space-1));
    line-height: var(--ui-button-line-height, 1.25);
    min-block-size: var(--ui-button-min-block-size, auto);
    padding: var(--ui-button-padding-block, var(--ui-space-1))
        var(--ui-button-padding-inline, var(--ui-space-2));
}

/* The label takes the free width of a stretched button; the button's
   centred text then centres the label. */
:is(ui-button, [ui-button]) > :is(button, a) > span:not([part])
{
    display: block;
    flex: 1 1 auto;
}

:is(ui-button, [ui-button]) > :is(button, a) > [part~="prefix"],
:is(ui-button, [ui-button]) > :is(button, a) > [part~="suffix"]
{
    display: block;
    flex: none;
}

/* An icon in an affix follows the button's font size and text colour.
   Without a size, an inline SVG falls back to 300 by 150 pixels. */
:is(ui-button, [ui-button]) > :is(button, a) > [part~="prefix"] > svg,
:is(ui-button, [ui-button]) > :is(button, a) > [part~="suffix"] > svg
{
    block-size: 1em;
    display: block;
    inline-size: 1em;
}

/* Outlined and text read their own --ui-button-outlined-* and
   --ui-button-text-* colours, so a filled colour set on an ancestor does not
   reach them. Radius, sizes, padding and font stay shared. */
:is(ui-button, [ui-button])[variant="outlined" i] > :is(button, a)
{
    background: var(--ui-button-outlined-background, transparent);
    border-color: var(--ui-button-outlined-border-color, currentColor);
    color: var(--ui-button-outlined-color, var(--ui-color-accent));
}

:is(ui-button, [ui-button])[variant="text" i] > :is(button, a)
{
    background: var(--ui-button-text-background, transparent);
    border-color: var(--ui-button-text-border-color, transparent);
    color: var(--ui-button-text-color, var(--ui-color-accent));
}

/* Hover and press lay the text colour over the background: 8% on hover,
   16% while pressed. */
:is(ui-button, [ui-button]) > :is(button, a):hover:not(:disabled)
{
    background: var(--ui-button-hover-background, color-mix(in srgb, var(--ui-button-color, var(--ui-color-surface)) 8%,
        var(--ui-button-background, var(--ui-color-accent))));
}

:is(ui-button, [ui-button]) > :is(button, a):active:not(:disabled)
{
    background: color-mix(in srgb, var(--ui-button-color, var(--ui-color-surface)) 16%,
        var(--ui-button-background, var(--ui-color-accent)));
}

:is(ui-button, [ui-button])[variant="outlined" i] > :is(button, a):hover:not(:disabled)
{
    background: var(--ui-button-outlined-hover-background,
        color-mix(in srgb, var(--ui-button-outlined-color, var(--ui-color-accent)) 8%,
        var(--ui-button-outlined-background, transparent)));
}

:is(ui-button, [ui-button])[variant="outlined" i] > :is(button, a):active:not(:disabled)
{
    background: color-mix(in srgb, var(--ui-button-outlined-color, var(--ui-color-accent)) 16%,
        var(--ui-button-outlined-background, transparent));
}

:is(ui-button, [ui-button])[variant="text" i] > :is(button, a):hover:not(:disabled)
{
    background: var(--ui-button-text-hover-background,
        color-mix(in srgb, var(--ui-button-text-color, var(--ui-color-accent)) 8%,
        var(--ui-button-text-background, transparent)));
}

:is(ui-button, [ui-button])[variant="text" i] > :is(button, a):active:not(:disabled)
{
    background: color-mix(in srgb, var(--ui-button-text-color, var(--ui-color-accent)) 16%,
        var(--ui-button-text-background, transparent));
}

:is(ui-button, [ui-button]) > :is(button, a):focus-visible
{
    outline: 2px solid var(--ui-color-accent);
    outline-offset: 2px;
}

:is(ui-button, [ui-button]) > button:disabled
{
    opacity: 0.6;
}

/* Forced colours replace the accent background with Canvas but keep a
   system colour such as Canvas in color (Chromium), so the filled label would
   vanish. ButtonText stays readable on the forced background. */
@media (forced-colors: active)
{
    :is(ui-button, [ui-button]) > :is(button, a)
    {
        color: ButtonText;
    }
}

@media (prefers-reduced-motion: no-preference)
{
    :is(ui-button, [ui-button]) > :is(button, a)
    {
        transition: background-color 150ms ease-out;
    }
}

/* ui-case-study-card: one case study as a card. The title slot holds a
   heading with one link that covers the card. The facts slot takes one div
   per fact, each with a dt and a dd, so role, period, starting point,
   contribution and result are always named, never implied. The page
   chooses which facts it shows and writes their labels. */
:is(ui-case-study-card, [ui-case-study-card])
{
    display: block;
}

:is(ui-case-study-card, [ui-case-study-card]) > article
{
    background: var(--ui-card-background, var(--ui-color-surface));
    border: 1px solid var(--ui-card-border-color, var(--ui-color-border));
    border-radius: var(--ui-card-radius, var(--ui-radius-1));
    box-sizing: border-box;
    color: var(--ui-color-text);
    display: grid;
    gap: var(--ui-card-gap, var(--ui-space-2));
    inline-size: 100%;
    padding: var(--ui-card-padding, var(--ui-space-3));
    position: relative;
}

:is(ui-case-study-card, [ui-case-study-card]) > article > [part~="title"] > :is(h1, h2, h3, h4, h5, h6)
{
    font-family: var(--ui-font-heading);
    font-size: var(--ui-card-title-size, 1.375rem);
    line-height: 1.2;
    margin: 0;
}

:is(ui-case-study-card, [ui-case-study-card]) > article > [part~="title"] a
{
    color: inherit;
    text-decoration: none;
}

:is(ui-case-study-card, [ui-case-study-card]) > article > [part~="title"] a::after
{
    content: "";
    inset: 0;
    position: absolute;
}

:is(ui-case-study-card, [ui-case-study-card]) > article:has(a:focus-visible)
{
    outline: 2px solid var(--ui-color-accent);
    outline-offset: 2px;
}

:is(ui-case-study-card, [ui-case-study-card]) > article > [part~="summary"]
{
    color: var(--ui-color-muted);
}

:is(ui-case-study-card, [ui-case-study-card]) > article > dl
{
    display: grid;
    gap: var(--ui-space-1) var(--ui-space-3);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 10rem), 1fr));
    margin: 0;
}

:is(ui-case-study-card, [ui-case-study-card]) > article > dl > div
{
    min-inline-size: 0;
}

:is(ui-case-study-card, [ui-case-study-card]) > article > dl dt
{
    color: var(--ui-color-muted);
    font-size: 0.8125em;
}

:is(ui-case-study-card, [ui-case-study-card]) > article > dl dd
{
    margin: 0;
}

/* ui-container: centres content in a column of one maximum width with side
   gutters. Host attribute size: narrow, wide or full; without it the
   column is --ui-container-size wide. */
:is(ui-container, [ui-container])
{
    box-sizing: border-box;
    display: block;
    inline-size: min(100%, var(--ui-container-size));
    margin-inline: auto;
    padding-inline: var(--ui-container-gutter);
}

:is(ui-container, [ui-container])[size="narrow" i]
{
    inline-size: min(100%, var(--ui-container-narrow-size, 44rem));
}

:is(ui-container, [ui-container])[size="wide" i]
{
    inline-size: min(100%, var(--ui-container-wide-size, 90rem));
}

:is(ui-container, [ui-container])[size="full" i]
{
    inline-size: 100%;
}

/* ui-footer: the end of a page. The default slot holds the page's link
   groups; the legal slot holds the line below them. Columns follow the
   available width like ui-grid. */
:is(ui-footer, [ui-footer])
{
    background: var(--ui-footer-background, transparent);
    color: var(--ui-footer-color, var(--ui-color-muted));
    display: block;
}

:is(ui-footer, [ui-footer]) > footer
{
    box-sizing: border-box;
    display: grid;
    gap: var(--ui-footer-gap, var(--ui-space-3));
    padding-block: var(--ui-footer-padding-block, var(--ui-space-4));
    padding-inline: var(--ui-container-gutter);
}

:is(ui-footer, [ui-footer]) > footer > [part~="body"]
{
    display: grid;
    gap: var(--ui-footer-gap, var(--ui-space-3));
    grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--ui-footer-column-min, 12rem)), 1fr));
}

:is(ui-footer, [ui-footer]) > footer > [part~="legal"]
{
    font-size: 0.875em;
}

/* ui-grid: equal columns that wrap by themselves. A column is at least
   --ui-grid-min wide (default 16rem), so the column count follows the
   available width without a breakpoint. Host attribute gap: 1 to 6. */
:is(ui-grid, [ui-grid])
{
    display: grid;
    gap: var(--ui-grid-gap, var(--ui-space-3));
    grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--ui-grid-min, 16rem)), 1fr));
}

:is(ui-grid, [ui-grid])[gap="1"] { --ui-grid-gap: var(--ui-space-1); }
:is(ui-grid, [ui-grid])[gap="2"] { --ui-grid-gap: var(--ui-space-2); }
:is(ui-grid, [ui-grid])[gap="3"] { --ui-grid-gap: var(--ui-space-3); }
:is(ui-grid, [ui-grid])[gap="4"] { --ui-grid-gap: var(--ui-space-4); }
:is(ui-grid, [ui-grid])[gap="5"] { --ui-grid-gap: var(--ui-space-5); }
:is(ui-grid, [ui-grid])[gap="6"] { --ui-grid-gap: var(--ui-space-6); }

/* A grid item may shrink below its content. */
:is(ui-grid, [ui-grid]) > *
{
    min-inline-size: 0;
}

/* ui-header: the bar at the top of a page with a brand, a navigation and
   actions. It lays the three regions out and nothing else; what they hold
   and how a navigation collapses is the page's decision. Host attribute
   sticky keeps the bar at the top edge while scrolling. */
:is(ui-header, [ui-header])
{
    background: var(--ui-header-background, var(--ui-color-surface));
    color: var(--ui-color-text);
    display: block;
}

:is(ui-header, [ui-header])[sticky]
{
    inset-block-start: 0;
    position: sticky;
    z-index: var(--ui-header-z-index, 10);
}

:is(ui-header, [ui-header]) > header
{
    align-items: center;
    box-sizing: border-box;
    display: flex;
    flex-wrap: wrap;
    gap: var(--ui-header-gap, var(--ui-space-2));
    min-block-size: var(--ui-header-height, auto);
    padding-block: var(--ui-header-padding-block, var(--ui-space-2));
    padding-inline: var(--ui-header-padding-inline, var(--ui-container-gutter));
}

:is(ui-header, [ui-header]) > header > nav
{
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: var(--ui-header-nav-gap, var(--ui-space-3));
}

/* Actions sit at the end of the row. */
:is(ui-header, [ui-header]) > header > [part~="actions"]
{
    align-items: center;
    display: flex;
    gap: var(--ui-space-1);
    margin-inline-start: var(--ui-header-actions-margin-inline-start, auto);
}

/* ui-project-card: a compact card for one product or project. The title
   slot holds a heading with one link; that link covers the whole card, so
   the card has a single focus stop and a large target. Other links in the
   meta part stay clickable above it. The status slot shows the state of the
   work, for example a ui-tag. */
:is(ui-project-card, [ui-project-card])
{
    display: block;
}

:is(ui-project-card, [ui-project-card]) > article
{
    background: var(--ui-card-background, var(--ui-color-surface));
    border: 1px solid var(--ui-card-border-color, var(--ui-color-border));
    border-radius: var(--ui-card-radius, var(--ui-radius-1));
    box-sizing: border-box;
    color: var(--ui-color-text);
    display: grid;
    grid-template-rows: auto 1fr;
    inline-size: 100%;
    overflow: hidden;
    position: relative;
}

:is(ui-project-card, [ui-project-card]) > article > [part~="media"] > :is(img, picture, video, svg)
{
    block-size: auto;
    display: block;
    inline-size: 100%;
}

:is(ui-project-card, [ui-project-card]) > article > [part~="body"]
{
    display: grid;
    align-content: start;
    gap: var(--ui-card-gap, var(--ui-space-1));
    padding: var(--ui-card-padding, var(--ui-space-2));
}

:is(ui-project-card, [ui-project-card]) > article > [part~="body"] > [part~="title"] > :is(h1, h2, h3, h4, h5, h6)
{
    font-size: var(--ui-card-title-size, 1.125rem);
    line-height: 1.25;
    margin: 0;
}

:is(ui-project-card, [ui-project-card]) > article > [part~="body"] > [part~="title"] a
{
    color: inherit;
    text-decoration: none;
}

:is(ui-project-card, [ui-project-card]) > article > [part~="body"] > [part~="title"] a::after
{
    content: "";
    inset: 0;
    position: absolute;
}

:is(ui-project-card, [ui-project-card]) > article:has(a:focus-visible)
{
    outline: 2px solid var(--ui-color-accent);
    outline-offset: 2px;
}

:is(ui-project-card, [ui-project-card]) > article > [part~="body"] > [part~="summary"]
{
    color: var(--ui-color-muted);
}

:is(ui-project-card, [ui-project-card]) > article > [part~="body"] > [part~="meta"]
{
    font-size: 0.875em;
    position: relative;
}

@media (prefers-reduced-motion: no-preference)
{
    :is(ui-project-card, [ui-project-card]) > article
    {
        transition: border-color var(--ui-duration) var(--ui-ease);
    }
}

:is(ui-project-card, [ui-project-card]) > article:hover
{
    border-color: var(--ui-card-hover-border-color, var(--ui-color-text));
}

/* ui-prose: typography for flowing content such as an article or a case
   study. It sets a readable measure and the rhythm between blocks and
   styles the elements an author writes by hand. */
:is(ui-prose, [ui-prose])
{
    display: block;
    font-family: var(--ui-prose-font, var(--ui-font-body));
    line-height: var(--ui-prose-line-height, 1.65);
    max-inline-size: var(--ui-measure);
}

:is(ui-prose, [ui-prose]) > * + *
{
    margin-block-start: var(--ui-prose-gap, 1.25em);
}

:is(ui-prose, [ui-prose]) :is(h1, h2, h3, h4)
{
    font-family: var(--ui-font-heading);
    line-height: 1.2;
    text-wrap: balance;
}

:is(ui-prose, [ui-prose]) :is(h2, h3, h4)
{
    margin-block-start: 2em;
}

:is(ui-prose, [ui-prose]) :is(h2, h3, h4) + *
{
    margin-block-start: 0.75em;
}

:is(ui-prose, [ui-prose]) a
{
    color: var(--ui-link-color, var(--ui-color-accent));
    text-underline-offset: 0.2em;
}

:is(ui-prose, [ui-prose]) a:focus-visible
{
    outline: 2px solid var(--ui-color-accent);
    outline-offset: 2px;
}

:is(ui-prose, [ui-prose]) :is(ul, ol)
{
    padding-inline-start: 1.5em;
}

:is(ui-prose, [ui-prose]) li + li
{
    margin-block-start: 0.4em;
}

:is(ui-prose, [ui-prose]) blockquote
{
    border-inline-start: 2px solid var(--ui-color-border);
    margin-inline: 0;
    padding-inline-start: 1em;
}

:is(ui-prose, [ui-prose]) :is(code, kbd, pre)
{
    font-family: var(--ui-font-mono);
    font-size: 0.9em;
}

:is(ui-prose, [ui-prose]) pre
{
    background: var(--ui-color-surface-raised);
    border-radius: var(--ui-radius-1);
    overflow-x: auto;
    padding: var(--ui-space-2);
}

:is(ui-prose, [ui-prose]) hr
{
    border: 0;
    border-block-start: 1px solid var(--ui-color-border);
}

:is(ui-prose, [ui-prose]) img
{
    block-size: auto;
    max-inline-size: 100%;
}

:is(ui-prose, [ui-prose]) table
{
    border-collapse: collapse;
    display: block;
    overflow-x: auto;
}

:is(ui-prose, [ui-prose]) :is(th, td)
{
    border-block-end: 1px solid var(--ui-color-border);
    padding: 0.5em 0.75em;
    text-align: start;
}

/* ui-section: a titled block of a page with vertical padding. The header
   slot holds the heading; the caller chooses its level. Host attribute
   variant: tinted lays a raised surface behind the section. */
:is(ui-section, [ui-section])
{
    display: block;
    padding-block: var(--ui-section-padding-block, var(--ui-space-5));
}

:is(ui-section, [ui-section])[variant="tinted" i]
{
    background: var(--ui-section-background, var(--ui-color-surface-raised));
}

:is(ui-section, [ui-section]) > section
{
    display: grid;
    gap: var(--ui-section-gap, var(--ui-space-3));
}

:is(ui-section, [ui-section]) > section > [part~="body"]
{
    min-inline-size: 0;
}

ui-sheet
{
    display: contents;
}

ui-sheet > section
{
    background: var(--ui-color-surface);
    color: var(--ui-color-text);
    display: grid;
    gap: var(--ui-space-2);
    inline-size: var(--ui-sheet-inline-size);
    /* inline-size sets the content box, so the padding comes on top. The
       maximum keeps the padded section inside its container. */
    max-inline-size: calc(100% - 2 * var(--ui-space-2));
    padding: var(--ui-space-2);
}

ui-sheet > section > header,
ui-sheet > section > footer
{
    display: flex;
    gap: var(--ui-space-1);
}

/* body: the default slot between header and footer */
ui-sheet > section > [part~="body"]
{
    min-inline-size: 0;
}

[data-ui-sheet-stack]
{
    display: grid;
}

/* The host has display: contents, so the section is the grid item. */
[data-ui-sheet-stack] > ui-sheet > section
{
    grid-area: 1 / 1;
}

/* ui-stack: children one after another with one gap. Host attributes:
   gap (1 to 6, the --ui-space-n steps), direction (row; default column),
   align (start, center, end) and justify (between, center, end). */
:is(ui-stack, [ui-stack])
{
    display: flex;
    flex-direction: column;
    gap: var(--ui-stack-gap, var(--ui-space-2));
}

:is(ui-stack, [ui-stack])[gap="1"] { --ui-stack-gap: var(--ui-space-1); }
:is(ui-stack, [ui-stack])[gap="2"] { --ui-stack-gap: var(--ui-space-2); }
:is(ui-stack, [ui-stack])[gap="3"] { --ui-stack-gap: var(--ui-space-3); }
:is(ui-stack, [ui-stack])[gap="4"] { --ui-stack-gap: var(--ui-space-4); }
:is(ui-stack, [ui-stack])[gap="5"] { --ui-stack-gap: var(--ui-space-5); }
:is(ui-stack, [ui-stack])[gap="6"] { --ui-stack-gap: var(--ui-space-6); }

:is(ui-stack, [ui-stack])[direction="row" i]
{
    align-items: center;
    flex-direction: row;
    flex-wrap: wrap;
}

:is(ui-stack, [ui-stack])[align="start" i] { align-items: flex-start; }
:is(ui-stack, [ui-stack])[align="center" i] { align-items: center; }
:is(ui-stack, [ui-stack])[align="end" i] { align-items: flex-end; }
:is(ui-stack, [ui-stack])[justify="between" i] { justify-content: space-between; }
:is(ui-stack, [ui-stack])[justify="center" i] { justify-content: center; }
:is(ui-stack, [ui-stack])[justify="end" i] { justify-content: flex-end; }

/* ui-tag: a short label such as a status or a category. It is not
   interactive. Host attribute variant: outlined (default) or filled. The
   colours come from --ui-tag-color and --ui-tag-background, so a page maps
   its own status colours onto them. */
:is(ui-tag, [ui-tag])
{
    display: inline-flex;
}

:is(ui-tag, [ui-tag]) > span
{
    border: 1px solid var(--ui-tag-color, currentColor);
    border-radius: var(--ui-tag-radius, var(--ui-radius-1));
    color: var(--ui-tag-color, inherit);
    font-size: var(--ui-tag-font-size, 0.8125em);
    line-height: 1.25;
    padding: 0.125em 0.5em;
    white-space: nowrap;
}

:is(ui-tag, [ui-tag])[variant="filled" i] > span
{
    background: var(--ui-tag-background, var(--ui-color-text));
    border-color: transparent;
    color: var(--ui-tag-filled-color, var(--ui-color-surface));
}

@media (forced-colors: active)
{
    :is(ui-tag, [ui-tag]) > span
    {
        border-color: CanvasText;
        color: CanvasText;
    }
}

/* ui-text-link: a native link inside running text or a list. The link
   keeps its underline so it is not told apart by colour alone. Host
   attribute variant: plain shows the underline only on hover and focus. */
:is(ui-text-link, [ui-text-link])
{
    display: inline;
}

:is(ui-text-link, [ui-text-link]) > a
{
    color: var(--ui-link-color, var(--ui-color-accent));
    text-decoration: underline;
    text-decoration-thickness: 0.08em;
    text-underline-offset: 0.2em;
}

:is(ui-text-link, [ui-text-link]) > a:hover
{
    text-decoration-thickness: 0.14em;
}

:is(ui-text-link, [ui-text-link])[variant="plain" i] > a
{
    color: inherit;
    text-decoration-color: transparent;
}

:is(ui-text-link, [ui-text-link])[variant="plain" i] > a:hover,
:is(ui-text-link, [ui-text-link])[variant="plain" i] > a:focus-visible
{
    text-decoration-color: currentColor;
}

:is(ui-text-link, [ui-text-link]) > a:focus-visible
{
    outline: 2px solid var(--ui-color-accent);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: no-preference)
{
    :is(ui-text-link, [ui-text-link]) > a
    {
        transition: text-decoration-color var(--ui-duration) var(--ui-ease);
    }
}

/* ui-textfield: label, control row (prefix, input, suffix), help, error,
   count. The root is the ui-textfield element or any element with the
   ui-textfield attribute, so handwritten HTML gets the same rules as composed
   output. Parts are native elements found by structure: the label, the row
   div with the input, the elements before and after the input, the help and
   error elements by their part names, and the count as the root's output.
   Every selector walks from the root through child combinators, so a nested
   field or a component inside a slot is never matched. The row is
   div:not([slot]): before composition, caller children carry slot and are
   skipped; after composition, caller content sits inside a part, never as a
   root child. Rules set display explicitly, and help, error and count a
   smaller font. Sizes and colours come from --ui-textfield-* custom
   properties. They are read where they are used, so a caller may set them on
   any ancestor. */

/* One explicit column with a zero minimum. An implicit auto column would grow
   to the row's min-content width, which counts the input at its natural
   width, so a narrow host would not shrink the input. */
:is(ui-textfield, [ui-textfield])
{
    display: grid;
    gap: var(--ui-space-1);
    grid-template-columns: minmax(0, 1fr);
}

:is(ui-textfield, [ui-textfield]) > label
{
    display: block;
}

/* control row: prefix, input and suffix */
:is(ui-textfield, [ui-textfield]) > div:not([slot])
{
    align-items: center;
    background: var(--ui-textfield-background, var(--ui-color-surface));
    border: 1px solid var(--ui-textfield-border-color, currentColor);
    border-radius: var(--ui-textfield-radius, var(--ui-radius-1));
    color: var(--ui-textfield-color, var(--ui-color-text));
    display: flex;
    font-size: var(--ui-textfield-font-size, inherit);
    gap: var(--ui-textfield-affix-gap, var(--ui-textfield-padding, var(--ui-space-1)));
    min-block-size: var(--ui-textfield-min-block-size, auto);
    padding-inline: var(--ui-textfield-padding, var(--ui-space-1));
}

:is(ui-textfield, [ui-textfield]) > div:not([slot]) > input
{
    background: transparent;
    border: 0;
    color: inherit;
    flex: 1 1 auto;
    font: inherit;
    /* An explicit line height makes 1lh below independent of font metrics;
       1.25 is what line-height: normal gives for Inter. */
    line-height: 1.25;
    min-inline-size: 0;
    outline: none;
    padding: var(--ui-textfield-padding, var(--ui-space-1)) 0;
    text-align: var(--ui-textfield-text-align, start);
}

/* Unset, the placeholder takes the colour Chromium paints by default. */
:is(ui-textfield, [ui-textfield]) > div:not([slot]) > input::placeholder
{
    color: var(--ui-textfield-placeholder-color, #757575);
}

/* The native size attribute sets the width in characters. */
:is(ui-textfield, [ui-textfield]) > div:not([slot]):has(> input[size])
{
    justify-self: start;
}

/* Safari shows its contact AutoFill button even when the author marks the
   field as not contact data. Hide it only for autocomplete="off" and
   transaction-* tokens. The rule stays alone: other engines drop a whole
   rule with an unknown pseudo-element. Safari sets the button's display
   itself, so only !important overrides it. Absolute position takes the
   button out of the flow, because the hidden button otherwise reserved space
   and pushed end-aligned text away from the suffix. */
:is(ui-textfield, [ui-textfield]) > div:not([slot]) > input:is([autocomplete="off" i], [autocomplete^="transaction-" i], [autocomplete*=" transaction-" i])::-webkit-contacts-auto-fill-button
{
    display: none !important;
    pointer-events: none;
    position: absolute;
    right: 0;
    visibility: hidden;
}

/* prefix: before the input; suffix: after the input */
:is(ui-textfield, [ui-textfield]) > div:not([slot]) > :has(~ input),
:is(ui-textfield, [ui-textfield]) > div:not([slot]) > input ~ *
{
    display: block;
    flex: none;
}

/* An icon in an affix follows the field's font size and text colour. Without
   a size, an inline SVG falls back to 300 by 150 pixels. */
:is(ui-textfield, [ui-textfield]) > div:not([slot]) > :has(~ input) > svg,
:is(ui-textfield, [ui-textfield]) > div:not([slot]) > input ~ * > svg
{
    block-size: 1em;
    display: block;
    inline-size: 1em;
}

:is(ui-textfield, [ui-textfield])[variant="filled" i] > div:not([slot])
{
    background: var(--ui-textfield-background,
        color-mix(in srgb, var(--ui-color-text) 8%, var(--ui-color-surface)));
    border-color: transparent;
    border-block-end-color: var(--ui-textfield-border-color, currentColor);
    border-end-end-radius: 0;
    border-end-start-radius: 0;
}

/* Borderless drops the border but keeps its space in the padding, so the
   field keeps the height and text position of the other variants. */
:is(ui-textfield, [ui-textfield])[variant="borderless" i] > div:not([slot])
{
    background: var(--ui-textfield-background, transparent);
    border: 0;
    padding: 1px calc(var(--ui-textfield-padding, var(--ui-space-1)) + 1px);
}

:is(ui-textfield, [ui-textfield]) > [part~="help"],
:is(ui-textfield, [ui-textfield]) > [part~="error"],
:is(ui-textfield, [ui-textfield]) > output
{
    display: block;
    font-size: smaller;
}

/* The help element is written without inner whitespace in the template, so
   :empty matches when the caller gives no help text. */
:is(ui-textfield, [ui-textfield]) > [part~="help"]:empty,
:is(ui-textfield, [ui-textfield]) > [part~="error"]
{
    display: none;
}

:is(ui-textfield, [ui-textfield]) > output
{
    justify-self: end;
}

/* States come from the inner input through :has(), never from JS classes.
   :has() cannot nest, so each state names the child chain to the input. */

:is(ui-textfield, [ui-textfield]):has(> div:not([slot]) > input:required) > label::after
{
    content: " *";
}

:is(ui-textfield, [ui-textfield]):has(> div:not([slot]) > input:focus-visible) > div:not([slot])
{
    outline: 2px solid var(--ui-color-accent);
    outline-offset: 2px;
}

:is(ui-textfield, [ui-textfield]):has(> div:not([slot]) > input:disabled)
{
    opacity: 0.6;
}

:is(ui-textfield, [ui-textfield]):has(> div:not([slot]) > input:user-invalid) > div:not([slot])
{
    border-color: var(--ui-color-danger);
}

:is(ui-textfield, [ui-textfield])[variant="filled" i]:has(> div:not([slot]) > input:user-invalid) > div:not([slot])
{
    border-color: transparent;
    border-block-end-color: var(--ui-color-danger);
}

/* Without a border, a tint marks the invalid field next to the error text. */
:is(ui-textfield, [ui-textfield])[variant="borderless" i]:has(> div:not([slot]) > input:user-invalid) > div:not([slot])
{
    background: color-mix(in srgb, var(--ui-color-danger) 20%, transparent);
}

:is(ui-textfield, [ui-textfield]):has(> div:not([slot]) > input:user-invalid) > [part~="error"]
{
    color: var(--ui-color-danger);
    display: block;
}

/* Floating label (floating-label attribute, orthogonal to variant). The label
   and the control row share the first grid row. The row becomes a subgrid of
   three host columns (prefix, input, suffix), so the label can sit in the
   input column without knowing the prefix width. The row's padding and border
   go into the edge tracks, so the input column starts where the input starts.
   Affix gaps become margins, because an empty prefix track would otherwise
   still add a column gap. The label floats while the input has focus, a value
   or autofill. "Has a value" is :not(:placeholder-shown): the script puts
   placeholder=" " on the input when the host has no placeholder; plain HTML
   writes the placeholder itself. A real placeholder shows only while the
   input has focus, which is while the label floats. */

:is(ui-textfield, [ui-textfield])[floating-label]
{
    column-gap: 0;
    grid-template-columns: auto minmax(0, 1fr) auto;
}

:is(ui-textfield, [ui-textfield])[floating-label] > *
{
    grid-column: 1 / -1;
}

:is(ui-textfield, [ui-textfield])[floating-label] > div:not([slot])
{
    column-gap: 0;
    display: grid;
    grid-row: 1;
    grid-template-columns: subgrid;
}

:is(ui-textfield, [ui-textfield])[floating-label] > div:not([slot]) > :has(~ input)
{
    grid-column: 1;
    grid-row: 1;
    margin-inline-end: var(--ui-textfield-affix-gap, var(--ui-textfield-padding, var(--ui-space-1)));
}

:is(ui-textfield, [ui-textfield])[floating-label] > div:not([slot]) > input
{
    grid-column: 2;
    grid-row: 1;
}

:is(ui-textfield, [ui-textfield])[floating-label] > div:not([slot]) > input ~ *
{
    grid-column: 3;
    grid-row: 1;
    margin-inline-start: var(--ui-textfield-affix-gap, var(--ui-textfield-padding, var(--ui-space-1)));
}

/* At rest the label is centred in the field at the input's font size. A
   relative grid item resolves top: 50% against its grid area, the field.
   The inline padding is the notch around the floated label; the negative
   margin keeps the text at the input's start. */
:is(ui-textfield, [ui-textfield])[floating-label] > label
{
    align-self: start;
    font-size: var(--ui-textfield-font-size, inherit);
    grid-column: 2;
    grid-row: 1;
    justify-self: start;
    margin-inline-start: -0.25em;
    max-inline-size: 100%;
    overflow: hidden;
    padding-inline: 0.25em;
    pointer-events: none;
    position: relative;
    text-overflow: ellipsis;
    top: 50%;
    transform-origin: 0 50%;
    translate: 0 -50%;
    white-space: nowrap;
    z-index: 1;
}

@media (prefers-reduced-motion: no-preference)
{
    :is(ui-textfield, [ui-textfield])[floating-label] > label
    {
        transition: top 150ms ease-out, translate 150ms ease-out, scale 150ms ease-out;
    }
}

:is(ui-textfield, [ui-textfield])[floating-label]:not(:has(> div:not([slot]) > input:is(:focus, :autofill))) > div:not([slot]) > input::placeholder
{
    color: transparent;
}

/* Outlined: the floated label is centred on the top border. Its background
   cuts the notch into the border. */
:is(ui-textfield, [ui-textfield])[floating-label]:has(> div:not([slot]) > input:is(:focus, :not(:placeholder-shown), :autofill)) > label
{
    background: var(--ui-textfield-background, var(--ui-color-surface));
    scale: var(--ui-textfield-floating-label-scale, 0.75);
    top: 0;
}

/* Filled and borderless: the floated label takes the top of the field, one
   scaled line high, and the input text starts below it. The bottom padding
   gives back what the top padding took, so the field keeps its height as
   long as two paddings are at least one scaled line. */
:is(ui-textfield, [ui-textfield])[floating-label]:is([variant="filled" i], [variant="borderless" i]) > div:not([slot]) > input
{
    padding-block: calc(1lh * var(--ui-textfield-floating-label-scale, 0.75))
        max(0px, calc(2 * var(--ui-textfield-padding, var(--ui-space-1))
            - 1lh * var(--ui-textfield-floating-label-scale, 0.75)));
}

/* The origin stays the same at rest and floated, so the label does not jump
   when the transition reverses. */
:is(ui-textfield, [ui-textfield])[floating-label]:is([variant="filled" i], [variant="borderless" i]) > label
{
    transform-origin: 0 0;
}

:is(ui-textfield, [ui-textfield])[floating-label]:is([variant="filled" i], [variant="borderless" i]):has(> div:not([slot]) > input:is(:focus, :not(:placeholder-shown), :autofill)) > label
{
    background: transparent;
    translate: 0 0;
}

/* ui-theme-toggle: one button that cycles system, light and dark. All
   three icons sit in the button and the one matching data-current shows.
   Without JavaScript the system icon stays and the page follows
   prefers-color-scheme. */
:is(ui-theme-toggle, [ui-theme-toggle])
{
    display: inline-flex;
}

:is(ui-theme-toggle, [ui-theme-toggle]) > button
{
    align-items: center;
    background: var(--ui-theme-toggle-background, transparent);
    border: 1px solid var(--ui-theme-toggle-border-color, var(--ui-color-border));
    border-radius: var(--ui-theme-toggle-radius, 999px);
    color: var(--ui-theme-toggle-color, inherit);
    cursor: pointer;
    display: inline-flex;
    justify-content: center;
    block-size: var(--ui-theme-toggle-size, 2.5rem);
    inline-size: var(--ui-theme-toggle-size, 2.5rem);
    padding: 0;
}

:is(ui-theme-toggle, [ui-theme-toggle]) > button > svg
{
    block-size: var(--ui-theme-toggle-icon-size, 1.25rem);
    display: none;
    inline-size: var(--ui-theme-toggle-icon-size, 1.25rem);
}

:is(ui-theme-toggle, [ui-theme-toggle]) > button[data-current="system"] > [data-theme-icon="system"],
:is(ui-theme-toggle, [ui-theme-toggle]) > button[data-current="light"] > [data-theme-icon="light"],
:is(ui-theme-toggle, [ui-theme-toggle]) > button[data-current="dark"] > [data-theme-icon="dark"]
{
    display: block;
}

:is(ui-theme-toggle, [ui-theme-toggle]) > button:hover
{
    background: color-mix(in srgb, currentColor 8%, var(--ui-theme-toggle-background, transparent));
    color: var(--ui-theme-toggle-hover-color, var(--ui-theme-toggle-color, inherit));
}

:is(ui-theme-toggle, [ui-theme-toggle]) > button:focus-visible
{
    outline: 2px solid var(--ui-color-accent);
    outline-offset: 2px;
}
