{
  "library": "@pacit/components",
  "version": "0.2.0",
  "angular": "^22.0.0",
  "install": "npm install @pacit/components",
  "generated": "by apps/docs/tools/build-content.mjs from the tracked sources the site renders — every number is a gate’s, every description a JSDoc line or a card row",
  "site": {
    "component": "/components/<id>",
    "theming": "/theming",
    "trust": "/trust",
    "acr": "/acr"
  },
  "evidence": {
    "mutation": {
      "score": 90.76,
      "killed": 15966,
      "mutants": 17591
    },
    "requirements": {
      "enforced": 83,
      "partial": 15,
      "gap": 0,
      "total": 98
    },
    "decisions": 106,
    "lessons": 257,
    "engines": 3,
    "cost": {
      "measured": "2026-10-31",
      "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz",
      "record": "apps/docs/bench.snapshot.md"
    }
  },
  "testing": {
    "entrypoint": "@pacit/components/testing",
    "base": "PctHarness — the CDK’s ComponentHarness over the data-pct-part inventory",
    "loader": "TestbedHarnessEnvironment.loader(fixture) from '@angular/cdk/testing/testbed', or the CDK’s WebDriver environment",
    "methods": {
      "part": "the element of a part by its inventory name — typed; throws naming the parts drawn",
      "parts": "every element of a part; an empty list is an answer",
      "has": "whether the part is drawn right now",
      "text": "the part’s text, trimmed",
      "state": "a data-pct-* attribute read off the host — state('size') reads data-pct-size",
      "with": "a HarnessPredicate with the CDK’s filters (selector, ancestor)"
    },
    "helpers": {
      "part": "part(fixture, name): the part’s element in a plain fixture; throws naming what is there",
      "allParts": "allParts(fixture, name): every element of a part",
      "query": "query(fixture, selector): the first element of a selector; throws when none"
    },
    "contract": "One harness per component, a declaration over the parts contract: the host selector is the component’s own, verbatim, and the parts are exactly the ones the package draws — both held to the built package by the check-harness gate, with the union type the editor offers and the names on every component page. Each component lists its harnesses under harnesses."
  },
  "texts": {
    "provider": "providePctTexts",
    "entrypoint": "@pacit/components",
    "contract": "The strings a component prints by itself; English by default, overridden in part or in full, read at render time from a signal so a language can change without a reload. A dictionary for another language is an object typed PctTexts, so a key the library adds is a compile error in the application and never a blank string.",
    "keys": [
      {
        "key": "selectPlaceholder",
        "meaning": "Select: when nothing is chosen.",
        "default": "Select…",
        "readBy": [
          "select"
        ]
      },
      {
        "key": "selectEmpty",
        "meaning": "Select: when there is not a single option.",
        "default": "No options",
        "readBy": [
          "select"
        ]
      },
      {
        "key": "selectNoMatches",
        "meaning": "Select: when a filter has left nothing standing. A different sentence from selectEmpty because it is a different fact about the world — \"there is nothing to choose from\" is the list's state, \"nothing here answers what you typed\" is the question's, and a user who has just typed three letters is owed the second one.",
        "default": "No matches",
        "readBy": [
          "select"
        ]
      },
      {
        "key": "selectLoading",
        "meaning": "Select: while the list is still coming. The third fact an empty panel can carry, and the one that is not a conclusion — \"there is nothing\" and \"nothing matches\" are both answers, and a request in flight has given neither (0037).",
        "default": "Loading…",
        "readBy": [
          "select"
        ]
      },
      {
        "key": "selectSeparator",
        "meaning": "Multi-select: what stands between the chosen labels on the trigger. Punctuation is a string like any other — an Arabic list is separated by ، and a Japanese one by 、, so a comma written into the template would be a word of English in every language.",
        "default": ", ",
        "readBy": [
          "select"
        ]
      },
      {
        "key": "selectClear",
        "meaning": "Select: the accessible name of the control that takes the answer back. It draws a cross and nothing else, so this string is the only name it has — and one name serves both jobs the button does, because what it takes back is whatever the trigger is showing (0036): a second string for \"clear the question\" would be a distinction the user never sees, the button standing in one place and doing one thing.",
        "default": "Clear",
        "readBy": [
          "select"
        ]
      },
      {
        "key": "dialogClose",
        "meaning": "Dialog: the accessible name of the button that closes it. The button draws a cross and nothing else, so this string is the only name it has — a component's own string and not content the view authored, which is what puts it here rather than in an input.",
        "default": "Close",
        "readBy": [
          "dialog"
        ]
      },
      {
        "key": "dateOpen",
        "meaning": "Date: the accessible name of the button that opens the calendar, and the name of the panel it opens. One string in both places on purpose — the button says what it will show and the panel is that thing, so a second string would be a distinction the user never meets (0036's reading of one control doing one thing).",
        "default": "Choose date",
        "readBy": [
          "date",
          "calendar"
        ]
      },
      {
        "key": "datePreviousMonth",
        "meaning": "Date: the accessible name of the button that steps the calendar back a month.",
        "default": "Previous month",
        "readBy": [
          "date",
          "calendar"
        ]
      },
      {
        "key": "dateNextMonth",
        "meaning": "Date: the accessible name of the button that steps it forward a month.",
        "default": "Next month",
        "readBy": [
          "date",
          "calendar"
        ]
      },
      {
        "key": "dateDayLetter",
        "meaning": "Date: the letters a format hint is written with — d, m, y in English, d, m, r in Polish. They are texts and not constants because they are words: the hint dd.mm.yyyy is read as language, and a y in a Polish field is a letter that means nothing there.",
        "default": "d",
        "readBy": [
          "date",
          "datetime",
          "calendar"
        ]
      },
      {
        "key": "dateMonthLetter",
        "meaning": "Date: the letters a format hint is written with — d, m, y in English, d, m, r in Polish. They are texts and not constants because they are words: the hint dd.mm.yyyy is read as language, and a y in a Polish field is a letter that means nothing there.",
        "default": "m",
        "readBy": [
          "date",
          "datetime",
          "calendar"
        ]
      },
      {
        "key": "dateYearLetter",
        "meaning": "Date: the letters a format hint is written with — d, m, y in English, d, m, r in Polish. They are texts and not constants because they are words: the hint dd.mm.yyyy is read as language, and a y in a Polish field is a letter that means nothing there.",
        "default": "y",
        "readBy": [
          "date",
          "datetime",
          "calendar"
        ]
      },
      {
        "key": "dateMalformed",
        "meaning": "Date: the sentence the field says about text that is not a date. The control's own error, first in the message line ahead of the form's — the form sees null and would call three typed numbers \"required\" (0070).",
        "default": "Not a date",
        "readBy": [
          "date",
          "datetime",
          "calendar"
        ]
      },
      {
        "key": "timeOpen",
        "meaning": "Time: the accessible name of the button that opens the columns, and the name of the panel it opens — one string in both places, for the reason dateOpen gives.",
        "default": "Choose time",
        "readBy": [
          "time",
          "columns"
        ]
      },
      {
        "key": "timeHourLetter",
        "meaning": "Time: the letters a format hint is written with — h, m, s in English, g, m, s in Polish. Words and not constants, as the date's are; the ORDER, the separators and the day-period words are not here — those come from Intl, and the period slot is written with the field's own two words, which are what it shows and reads back (0086 §3).",
        "default": "h",
        "readBy": [
          "time",
          "datetime",
          "columns"
        ]
      },
      {
        "key": "timeMinuteLetter",
        "meaning": "Time: the letters a format hint is written with — h, m, s in English, g, m, s in Polish. Words and not constants, as the date's are; the ORDER, the separators and the day-period words are not here — those come from Intl, and the period slot is written with the field's own two words, which are what it shows and reads back (0086 §3).",
        "default": "m",
        "readBy": [
          "time",
          "datetime",
          "columns"
        ]
      },
      {
        "key": "timeSecondLetter",
        "meaning": "Time: the letters a format hint is written with — h, m, s in English, g, m, s in Polish. Words and not constants, as the date's are; the ORDER, the separators and the day-period words are not here — those come from Intl, and the period slot is written with the field's own two words, which are what it shows and reads back (0086 §3).",
        "default": "s",
        "readBy": [
          "time",
          "datetime",
          "columns"
        ]
      },
      {
        "key": "timeMalformed",
        "meaning": "Time: the sentence the field says about text that is not a time — the control's own error, first in the message line ahead of the form's, which sees null (0070).",
        "default": "Not a time",
        "readBy": [
          "time",
          "datetime",
          "columns"
        ]
      },
      {
        "key": "timeHours",
        "meaning": "Time: the accessible name of the column of hours. Each column of the panel is a listbox of its own and needs a name of its own — a column with none is a serious axe finding (0086, E5) — and the name is a word of the application's language, not of the locale's.",
        "default": "Hours",
        "readBy": [
          "time",
          "columns"
        ]
      },
      {
        "key": "timeMinutes",
        "meaning": "Time: the accessible name of the column of minutes.",
        "default": "Minutes",
        "readBy": [
          "time",
          "columns"
        ]
      },
      {
        "key": "timeSeconds",
        "meaning": "Time: the accessible name of the column of seconds, which stands where the step has them.",
        "default": "Seconds",
        "readBy": [
          "time",
          "columns"
        ]
      },
      {
        "key": "timePeriod",
        "meaning": "Time: the accessible name of the column of the two halves of the day, on a twelve-hour clock. The column's two rows are the locale's own words, read off the formatter; the name of the column is the application's.",
        "default": "AM/PM",
        "readBy": [
          "time",
          "columns"
        ]
      },
      {
        "key": "datetimeOpen",
        "meaning": "Datetime: the accessible name of the button that opens the panel of a calendar and columns, and the name of the panel — one string in both places, for the reason dateOpen gives.",
        "default": "Choose date and time",
        "readBy": [
          "datetime"
        ]
      },
      {
        "key": "datetimeDay",
        "meaning": "Datetime: the word each of the field's two texts is named by after the field's label — \"Starts, Day\" and then \"Starts, Time\". Two textboxes under a named group with no name of their own are a critical axe finding in all three engines, and two named by the label alone pass axe and leave a reader on the second one not told it is the time (0090, E2 and E3) — so the word is the record's rule, not the gate's.",
        "default": "Day",
        "readBy": [
          "datetime"
        ]
      },
      {
        "key": "datetimeTime",
        "meaning": "Datetime: the word each of the field's two texts is named by after the field's label — \"Starts, Day\" and then \"Starts, Time\". Two textboxes under a named group with no name of their own are a critical axe finding in all three engines, and two named by the label alone pass axe and leave a reader on the second one not told it is the time (0090, E2 and E3) — so the word is the record's rule, not the gate's.",
        "default": "Time",
        "readBy": [
          "datetime"
        ]
      },
      {
        "key": "datetimeIncomplete",
        "meaning": "Datetime: the sentence the field says when focus leaves it with one half holding a value and the other empty — the value is null until both do, and a required field would otherwise say \"required\" over a day the user can see. The control's own error, first in the message line, gated by no touch (0070); the two halves' junk keeps the date's and the time's own sentences.",
        "default": "Enter both a date and a time",
        "readBy": [
          "datetime"
        ]
      },
      {
        "key": "numberMalformed",
        "meaning": "Number: the same sentence for text that is not a number, which the field now keeps instead of clearing — a user is shown what they typed, and told what it is not (0070).",
        "default": "Not a number",
        "readBy": [
          "field",
          "text",
          "textarea",
          "number"
        ]
      },
      {
        "key": "fieldWarning",
        "meaning": "Field: the word a reader hears before a warning's sentence, hidden from the eye. ARIA has no property for a warning — aria-invalid stays off, because the value is allowed — so the glyph beside the line is a drawing and this word is what says \"warning\" to someone who cannot see it. It ends in a colon because it OPENS the sentence: Warning: Unusually large (0087).",
        "default": "Warning:",
        "readBy": [
          "field",
          "text",
          "textarea",
          "number",
          "date",
          "time",
          "datetime",
          "select",
          "checkbox",
          "radio",
          "switch",
          "slider",
          "calendar",
          "columns",
          "table",
          "datatable"
        ]
      },
      {
        "key": "toastDismiss",
        "meaning": "Toast: the accessible name of the cross that takes a message down. The button draws nothing but the cross, so this string is the only name it has — the dialog's dialogClose one component over, and deliberately a second key rather than a shared one: \"close\" is what a panel does and \"dismiss\" is what happens to a message, and a language that spells the two differently has nowhere else to say so.",
        "default": "Dismiss",
        "readBy": [
          "toast"
        ]
      },
      {
        "key": "toastRegion",
        "meaning": "Toast: what the stack of messages is called when a keyboard arrives at it as a region. The stack is a role=\"log\" that stands outside the page's reading order, so the region cycle ({@link PctRegions}) is how a keyboard reaches it in one hop — and a place the keyboard lands on with nothing to announce is a place the user has to guess at. It names the region and not the messages: what is IN it is read as it arrives, by the live region itself.",
        "default": "Notifications",
        "readBy": [
          "toast"
        ]
      },
      {
        "key": "drawerClose",
        "meaning": "Drawer: the accessible name of the cross that shuts a docked panel. The button draws nothing but the cross, so this string is the only name it has — and it is a key of its own rather than the dialog's, which the English default makes look like duplication and is not. A dialog is a window: it is CLOSED, and it leaves. A drawer stays part of the page with its text still findable (0047), so a language that spells \"shut this away\" differently from \"close this window\" has nowhere else to say so. One key serving both would freeze that distinction out of every translation at once, which is toastDismiss's argument at the next component.",
        "default": "Close",
        "readBy": [
          "drawer"
        ]
      },
      {
        "key": "paginationLabel",
        "meaning": "Pagination: the accessible name of the navigation landmark the pager is. A default in the application's language and nothing more — a page with two pagers (above and below a table) tells them apart through the ariaLabel input, not through this.",
        "default": "Pagination",
        "readBy": [
          "pagination"
        ]
      },
      {
        "key": "paginationPrevious",
        "meaning": "Pagination: the accessible name of the stepper that goes back one page. The button draws a chevron and nothing else, so this string is the only name it has. A key of its own and not the calendar's datePreviousMonth: one steps a pager and the other a grid of days, and a language that says the two differently has nowhere else to.",
        "default": "Previous page",
        "readBy": [
          "pagination"
        ]
      },
      {
        "key": "paginationNext",
        "meaning": "Pagination: the accessible name of the stepper that goes forward one page.",
        "default": "Next page",
        "readBy": [
          "pagination"
        ]
      },
      {
        "key": "chipRemove",
        "meaning": "Chips: the VERB half of the name of the control that takes a chosen value back. The button draws a cross and nothing else, and what it acts on is the chip's own projected text — so the two are joined by aria-labelledby and a reader hears \"Remove, In stock\". Until 2026-09-16 this string stood alone here, on the argument that the label beside the button was heard anyway; three readers were asked and none of them said it (lesson-218). Word order is the price: a language that puts the object first has the verb second, and this key cannot say so. A key of its own rather than the toast's toastDismiss or the dialog's dialogClose: \"remove\" is what happens to a chosen value, and a language that spells it apart from \"dismiss a message\" and \"close a window\" has nowhere else to say so (0051).",
        "default": "Remove",
        "readBy": [
          "chips"
        ]
      },
      {
        "key": "breadcrumbLabel",
        "meaning": "Breadcrumb: the accessible name of the navigation landmark the trail is. A default in the application's language and nothing more — a page with two trails tells them apart through the ariaLabel input, not through this. The pagination's key, one landmark over: two components, two landmarks, two names a translation may spell apart.",
        "default": "Breadcrumb",
        "readBy": [
          "breadcrumb"
        ]
      },
      {
        "key": "stepDone",
        "meaning": "Stepper: the suffix a done step carries for a reader — the check that marks it is a drawing the accessibility tree never sees, so this word is how \"behind you\" is said at all (0055). It rides INSIDE the step's content, after the application's own label, so the name stays \"Payment, Completed\" with the application's word first — never in an aria-label, which would replace the label instead of following it.",
        "default": "Completed",
        "readBy": [
          "stepper"
        ]
      },
      {
        "key": "chartEmpty",
        "meaning": "Chart: the message standing in the plot while it has nothing to draw — no series, or none with a reading in the window. The axes stay, so the reader sees where the data would be. An application with a better sentence (\"No measurements in this range\") projects it.",
        "default": "No data to show",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartGap",
        "meaning": "Chart: what the data table writes where a series has no reading — a gap, not a zero.",
        "default": "No reading",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartOn",
        "meaning": "Chart: the label of the upper end of a yes/no axis, and of a yes in the data table.",
        "default": "On",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartOff",
        "meaning": "Chart: the label of the lower end of a yes/no axis, and of a no in the data table.",
        "default": "Off",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartTime",
        "meaning": "Chart: the header of the data table's first column on a time axis.",
        "default": "Time",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartPosition",
        "meaning": "Chart: the header of the data table's first column on a numeric axis.",
        "default": "Position",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartAllHidden",
        "meaning": "Chart: the message of a chart whose every series is hidden.",
        "default": "Every series is hidden",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartNoCursor",
        "meaning": "Chart: what the cursor's range says while no reading is selected.",
        "default": "No reading selected",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartKeys",
        "meaning": "Chart: the keys of the cursor, one sentence, read as the range's description.",
        "default": "Arrow keys move from reading to reading, Page Up and Page Down a tenth of the view, Home and End to the first and last; Enter selects the bar under the cursor; Escape clears the cursor.",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartSum",
        "meaning": "Chart: the data table's foot that sums the columns.",
        "default": "Sum",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartMean",
        "meaning": "Chart: the data table's foot that averages the columns.",
        "default": "Mean",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartDifference",
        "meaning": "Chart: the tooltip's row with the difference between a bar and the compared period's.",
        "default": "Difference",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartReference",
        "meaning": "Chart: a reference line's name, when the consumer gives it none.",
        "default": "Reference line",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartSpan",
        "meaning": "Chart: a band's name, when the consumer gives it none.",
        "default": "Band",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartNow",
        "meaning": "Chart: the line of the present moment, when the consumer gives it no name.",
        "default": "Now",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartBand",
        "meaning": "Chart: a band of states' name, when the consumer gives it none.",
        "default": "States",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartSize",
        "meaning": "Chart: the name of a bubble's size in a tooltip and a table, when the consumer gives it none.",
        "default": "Size",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartOpen",
        "meaning": "Chart: a candle's opening value, in a tooltip and a table.",
        "default": "Open",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartLow",
        "meaning": "Chart: the low of a range, a candle or a box, in a tooltip and a table.",
        "default": "Low",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartHigh",
        "meaning": "Chart: the high of a range, a candle or a box, in a tooltip and a table.",
        "default": "High",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartQuartile1",
        "meaning": "Chart: a box's lower quartile, in a tooltip and a table.",
        "default": "Lower quartile",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartQuartile3",
        "meaning": "Chart: a box's upper quartile, in a tooltip and a table.",
        "default": "Upper quartile",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartOutliers",
        "meaning": "Chart: a box's values beyond its whiskers, in a tooltip and a table.",
        "default": "Outliers",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartLegend",
        "meaning": "Chart: the name of the legend's list of series.",
        "default": "Series",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartRemove",
        "meaning": "Chart: the legend's button that takes a series off the chart, named with the series.",
        "default": "Remove",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartZoom",
        "meaning": "Chart: the name of the bar of zoom buttons.",
        "default": "Zoom",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartZoomIn",
        "meaning": "Chart: the button that shows half as much of the horizontal axis.",
        "default": "Zoom in",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartZoomOut",
        "meaning": "Chart: the button that shows twice as much.",
        "default": "Zoom out",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartEarlier",
        "meaning": "Chart: the button that moves the window a quarter earlier.",
        "default": "Earlier",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartLater",
        "meaning": "Chart: the button that moves the window a quarter later.",
        "default": "Later",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "chartZoomReset",
        "meaning": "Chart: the button that returns the window to its default.",
        "default": "Back to the default view",
        "readBy": [
          "chart"
        ]
      },
      {
        "key": "tableEmpty",
        "meaning": "Data table: beside a table with no rows at all.",
        "default": "No rows",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableNoMatches",
        "meaning": "Data table: beside a table whose rows the quick filter or the application's own filter has all taken away — the question's state, not the list's, as selectNoMatches is.",
        "default": "No matches",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableLoading",
        "meaning": "Data table: beside a table whose rows are still coming, with none yet.",
        "default": "Loading…",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableYes",
        "meaning": "Data table: how a boolean cell that is true is read, and searched by the quick filter.",
        "default": "Yes",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableNo",
        "meaning": "Data table: how a boolean cell that is false is read, and searched by the quick filter.",
        "default": "No",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableRowNumber",
        "meaning": "Data table: the header of the column that numbers the rows, as a reader hears it; the eye sees #, which a reader would call a number sign.",
        "default": "Row",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableSortLater",
        "meaning": "Data table: a sort key after the first, as a reader hears it — the description of its header's button. {place} stands for its place in the order and {way} for tableSortAscending or tableSortDescending, so a language orders the sentence and punctuates it as it speaks: Sort key {place}, {way} is Sort key 2, descending.",
        "default": "Sort key {place}, {way}",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableSortAscending",
        "meaning": "Data table: the way of a sort key after the first, ascending.",
        "default": "ascending",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableSortDescending",
        "meaning": "Data table: the way of a sort key after the first, descending.",
        "default": "descending",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableDetails",
        "meaning": "Data table: the header of the column of buttons that open the rows' details.",
        "default": "Details",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableDetailsOpen",
        "meaning": "Data table: the name of a row's button while its details are closed.",
        "default": "Show details",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableDetailsClose",
        "meaning": "Data table: the name of a row's button while its details are open.",
        "default": "Hide details",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableGroupCount",
        "meaning": "Data table: how many rows a group holds, beside its label. {count} stands for the number in the table's locale.",
        "default": "({count})",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableTotal",
        "meaning": "Data table: the label of the foot that sums up every row in view.",
        "default": "Total",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableGroupEmpty",
        "meaning": "Data table: the label of the group of rows with no value in the column grouped by.",
        "default": "No value",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableGroupWithin",
        "meaning": "Data table: how a reader hears the heading of a group inside another. {group} stands for its label and count, {outer} for the label of the group it stands in.",
        "default": "{group}, in {outer}",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableSelectAll",
        "meaning": "Data table: the name of the header's checkbox, which picks every row the filter left.",
        "default": "Select all rows",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableSelectRow",
        "meaning": "Data table: the word a row's checkbox is named by, before the text of the row's first cell — Select Kitchen lamp.",
        "default": "Select",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableLoadFailed",
        "meaning": "Data table: what a block of rows a remote source failed to deliver says in their place.",
        "default": "These rows could not be loaded.",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableRetry",
        "meaning": "Data table: the button that asks a remote source for a failed block of rows again.",
        "default": "Retry",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableFilter",
        "meaning": "Data table: the name of a column's filter button, and the heading of its panel. {column} stands for the column's header.",
        "default": "Filter {column}",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableFiltered",
        "meaning": "Data table: the name of a column's filter button while the column is filtered, so a reader hears it without opening the panel. {column} stands for the column's header.",
        "default": "Filter {column}, filtered",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableClear",
        "meaning": "Data table: the button that clears a column's filter.",
        "default": "Clear",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableFrom",
        "meaning": "Data table: the lower end of a column's range filter. {column} stands for its header.",
        "default": "{column} from",
        "readBy": [
          "table",
          "datatable"
        ]
      },
      {
        "key": "tableTo",
        "meaning": "Data table: the upper end of a column's range filter. {column} stands for its header.",
        "default": "{column} to",
        "readBy": [
          "table",
          "datatable"
        ]
      }
    ],
    "template": {
      "selectPlaceholder": "Select…",
      "selectEmpty": "No options",
      "selectNoMatches": "No matches",
      "selectLoading": "Loading…",
      "selectSeparator": ", ",
      "selectClear": "Clear",
      "dialogClose": "Close",
      "dateOpen": "Choose date",
      "datePreviousMonth": "Previous month",
      "dateNextMonth": "Next month",
      "dateDayLetter": "d",
      "dateMonthLetter": "m",
      "dateYearLetter": "y",
      "dateMalformed": "Not a date",
      "timeOpen": "Choose time",
      "timeHourLetter": "h",
      "timeMinuteLetter": "m",
      "timeSecondLetter": "s",
      "timeMalformed": "Not a time",
      "timeHours": "Hours",
      "timeMinutes": "Minutes",
      "timeSeconds": "Seconds",
      "timePeriod": "AM/PM",
      "datetimeOpen": "Choose date and time",
      "datetimeDay": "Day",
      "datetimeTime": "Time",
      "datetimeIncomplete": "Enter both a date and a time",
      "numberMalformed": "Not a number",
      "fieldWarning": "Warning:",
      "toastDismiss": "Dismiss",
      "toastRegion": "Notifications",
      "drawerClose": "Close",
      "paginationLabel": "Pagination",
      "paginationPrevious": "Previous page",
      "paginationNext": "Next page",
      "chipRemove": "Remove",
      "breadcrumbLabel": "Breadcrumb",
      "stepDone": "Completed",
      "chartEmpty": "No data to show",
      "chartGap": "No reading",
      "chartOn": "On",
      "chartOff": "Off",
      "chartTime": "Time",
      "chartPosition": "Position",
      "chartAllHidden": "Every series is hidden",
      "chartNoCursor": "No reading selected",
      "chartKeys": "Arrow keys move from reading to reading, Page Up and Page Down a tenth of the view, Home and End to the first and last; Enter selects the bar under the cursor; Escape clears the cursor.",
      "chartSum": "Sum",
      "chartMean": "Mean",
      "chartDifference": "Difference",
      "chartReference": "Reference line",
      "chartSpan": "Band",
      "chartNow": "Now",
      "chartBand": "States",
      "chartSize": "Size",
      "chartOpen": "Open",
      "chartLow": "Low",
      "chartHigh": "High",
      "chartQuartile1": "Lower quartile",
      "chartQuartile3": "Upper quartile",
      "chartOutliers": "Outliers",
      "chartLegend": "Series",
      "chartRemove": "Remove",
      "chartZoom": "Zoom",
      "chartZoomIn": "Zoom in",
      "chartZoomOut": "Zoom out",
      "chartEarlier": "Earlier",
      "chartLater": "Later",
      "chartZoomReset": "Back to the default view",
      "tableEmpty": "No rows",
      "tableNoMatches": "No matches",
      "tableLoading": "Loading…",
      "tableYes": "Yes",
      "tableNo": "No",
      "tableRowNumber": "Row",
      "tableSortLater": "Sort key {place}, {way}",
      "tableSortAscending": "ascending",
      "tableSortDescending": "descending",
      "tableDetails": "Details",
      "tableDetailsOpen": "Show details",
      "tableDetailsClose": "Hide details",
      "tableGroupCount": "({count})",
      "tableTotal": "Total",
      "tableGroupEmpty": "No value",
      "tableGroupWithin": "{group}, in {outer}",
      "tableSelectAll": "Select all rows",
      "tableSelectRow": "Select",
      "tableLoadFailed": "These rows could not be loaded.",
      "tableRetry": "Retry",
      "tableFilter": "Filter {column}",
      "tableFiltered": "Filter {column}, filtered",
      "tableClear": "Clear",
      "tableFrom": "{column} from",
      "tableTo": "{column} to"
    }
  },
  "components": [
    {
      "id": "button",
      "classes": [
        "PctButton"
      ],
      "role": "button",
      "category": "Actions & navigation",
      "status": "released",
      "entrypoint": "@pacit/components/button",
      "selectors": [
        "button[pctButton]",
        "a[pctButton]"
      ],
      "harnesses": [
        "PctButtonHarness"
      ],
      "docs": "/components/button",
      "summary": "A button in five faces, four tones and three sizes — or a square around one glyph — drawn on the platform's own <button>, or on its <a>, when the control navigates.",
      "usage": {
        "code": "<button pctButton>Save</button> <a pctButton href=\"/start\">Get started</a>\n<button pctButton iconOnly variant=\"ghost\" aria-label=\"Delete the draft\">\n  <pct-icon icon=\"trash\" />\n</button>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "faces",
          "title": "Faces",
          "code": "import { Component } from '@angular/core';\nimport { PctButton } from '@pacit/components/button';\n\n/**\n * Faces\n *\n * `variant` picks the paint. Solid carries the primary action, outline and ghost sit beside\n * it, soft is the quiet tint — and hero is the one that drifts, reserved for the single most\n * important call on a page.\n */\n@Component({\n  selector: 'demo-button-faces',\n  imports: [PctButton],\n  styles:\n    ':host { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }',\n  template: `\n    <button pctButton>Solid</button>\n    <button pctButton variant=\"outline\">Outline</button>\n    <button pctButton variant=\"ghost\">Ghost</button>\n    <button pctButton variant=\"soft\">Soft</button>\n    <button pctButton variant=\"hero\">Hero</button>\n  `,\n})\nexport class ButtonFacesDemo {}"
        },
        {
          "key": "tones",
          "title": "Tones",
          "code": "import { Component, computed, signal } from '@angular/core';\nimport { PctButton } from '@pacit/components/button';\nimport { PctTone } from '@pacit/components/core';\nimport { PctRadio, PctRadioGroup } from '@pacit/components/radio';\n\n/**\n * Tones\n *\n * `tone` picks which of the skin's families the face paints from — the same four names the\n * toast and the progress bar wear. Move the choice and watch the four faces follow: there is\n * no `primary` tone, because a button without one already is the brand, and `None` is what\n * every button here is by default.\n *\n * The fifth face is missing from the row on purpose. `hero` is the brand gradient and takes\n * no tone at all; ask it for one and it says so in dev mode rather than quietly ignoring you.\n *\n * The colour is never the message. \"Delete account\" says danger in words, and that is what a\n * reader hears — a red button labelled \"OK\" tells nobody anything they could not see.\n */\n@Component({\n  selector: 'demo-button-tones',\n  imports: [PctButton, PctRadio, PctRadioGroup],\n  styles: `\n    :host {\n      display: flex;\n      flex-direction: column;\n      gap: 20px;\n    }\n    .row {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 12px;\n      align-items: center;\n    }\n  `,\n  template: `\n    <pct-radio-group ariaLabel=\"Tone\" [(value)]=\"choice\">\n      <pct-radio value=\"none\">None</pct-radio>\n      <pct-radio value=\"danger\">danger</pct-radio>\n      <pct-radio value=\"warning\">warning</pct-radio>\n      <pct-radio value=\"success\">success</pct-radio>\n      <pct-radio value=\"info\">info</pct-radio>\n    </pct-radio-group>\n\n    <div class=\"row\">\n      <button pctButton [tone]=\"tone()\">Solid</button>\n      <button pctButton variant=\"outline\" [tone]=\"tone()\">Outline</button>\n      <button pctButton variant=\"ghost\" [tone]=\"tone()\">Ghost</button>\n      <button pctButton variant=\"soft\" [tone]=\"tone()\">Soft</button>\n    </div>\n  `,\n})\nexport class ButtonTonesDemo {\n  readonly choice = signal<PctTone | 'none'>('danger');\n\n  /** `none` is the absence of a tone, which is what the input takes: there is no member for it. */\n  protected readonly tone = computed(() => {\n    const chosen = this.choice();\n    return chosen === 'none' ? null : chosen;\n  });\n}"
        },
        {
          "key": "destructive",
          "title": "A destructive action, and the way out of it",
          "code": "import { Component } from '@angular/core';\nimport { PctButton } from '@pacit/components/button';\n\n/**\n * A destructive action, and the way out of it\n *\n * The tone is for the one press that cannot be taken back — and it works because of what\n * stands beside it. The way out is the quiet face with no tone at all: a row where both\n * buttons shout has no shape, and a reader who cannot see the red is left with the two\n * labels, which is why the labels have to be the ones carrying the difference.\n *\n * `soft` is the same tone at a lower volume, for the destructive action that is not the\n * point of the screen — removing one row of a table rather than closing the account.\n */\n@Component({\n  selector: 'demo-button-destructive',\n  imports: [PctButton],\n  styles: `\n    :host {\n      display: flex;\n      flex-direction: column;\n      gap: 20px;\n    }\n    .row {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 12px;\n      align-items: center;\n    }\n    .caption {\n      margin: 0;\n      font-size: 13px;\n      opacity: 0.75;\n    }\n  `,\n  template: `\n    <div>\n      <div class=\"row\">\n        <button pctButton tone=\"danger\">Delete account</button>\n        <button pctButton variant=\"ghost\">Keep it</button>\n      </div>\n      <p class=\"caption\">\n        The dialog's own footer: one press that ends it, one that does not.\n      </p>\n    </div>\n    <div>\n      <div class=\"row\">\n        <button pctButton variant=\"soft\" tone=\"danger\" size=\"sm\">\n          Remove row\n        </button>\n        <button pctButton variant=\"soft\" tone=\"warning\" size=\"sm\">\n          Overwrite draft\n        </button>\n      </div>\n      <p class=\"caption\">\n        The same tones where the action is not the point of the screen.\n      </p>\n    </div>\n  `,\n})\nexport class ButtonDestructiveDemo {}"
        },
        {
          "key": "link",
          "title": "Links wearing the face",
          "code": "import { Component } from '@angular/core';\nimport { PctButton } from '@pacit/components/button';\n\n/**\n * Links wearing the face\n *\n * A control that goes somewhere is an `<a>`, whatever it looks like: the middle click, the\n * address bar's preview and a crawler's road through the site all come with the element and\n * with nothing else. `pctButton` paints it and leaves the role alone — only the underline\n * goes, because the face answers a pointer with its own background. A disabled link is the\n * one thing HTML has no mechanism for, so the component says `aria-disabled` and refuses the\n * press itself.\n */\n@Component({\n  selector: 'demo-button-link',\n  imports: [PctButton],\n  styles:\n    ':host { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }',\n  template: `\n    <a pctButton variant=\"hero\" href=\"#start\">Get started</a>\n    <a pctButton variant=\"outline\" href=\"#components\">Browse components</a>\n    <a pctButton variant=\"ghost\" href=\"#changelog\" disabled>Changelog</a>\n  `,\n})\nexport class ButtonLinkDemo {}"
        },
        {
          "key": "sizes",
          "title": "Sizes",
          "code": "import { Component } from '@angular/core';\nimport { PctButton } from '@pacit/components/button';\n\n/**\n * Sizes\n *\n * Three heights — 28, 36 and 44 px — shared with every field, so a button and an input on\n * the same row line up to the pixel. The default comes from `providePctConfig`, not from the\n * template.\n */\n@Component({\n  selector: 'demo-button-sizes',\n  imports: [PctButton],\n  styles:\n    ':host { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }',\n  template: `\n    <button pctButton size=\"sm\">Small</button>\n    <button pctButton>Medium</button>\n    <button pctButton size=\"lg\">Large</button>\n  `,\n})\nexport class ButtonSizesDemo {}"
        },
        {
          "key": "states",
          "title": "Loading and disabled",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctButton } from '@pacit/components/button';\n\n/**\n * Loading and disabled\n *\n * Two states, kept apart on purpose. `loading` says \"working\": the spinner takes the face's\n * own colour and the element reports `aria-busy`. `disabled` says \"cannot be clicked\". A\n * queued click is a legitimate pattern, so loading alone never blocks — press Save to watch\n * both at once.\n */\n@Component({\n  selector: 'demo-button-states',\n  imports: [PctButton],\n  styles:\n    ':host { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }',\n  template: `\n    <button\n      pctButton\n      [loading]=\"saving()\"\n      [disabled]=\"saving()\"\n      (click)=\"save()\"\n    >\n      {{ saving() ? 'Saving…' : 'Save' }}\n    </button>\n    <button pctButton variant=\"outline\" loading>Checking</button>\n    <button pctButton disabled>Disabled</button>\n    <button pctButton variant=\"outline\" disabled>Disabled</button>\n  `,\n})\nexport class ButtonStatesDemo {\n  protected readonly saving = signal(false);\n\n  protected save(): void {\n    this.saving.set(true);\n    setTimeout(() => this.saving.set(false), 1500);\n  }\n}"
        },
        {
          "key": "icon-only",
          "title": "Icon only",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctButton } from '@pacit/components/button';\nimport { PctIcon, providePctIcons } from '@pacit/components/icon';\nimport { svgIcons } from '@pacit/components/svg-icon';\nimport { PctTooltip } from '@pacit/components/tooltip';\nimport { Copy, Download, Pencil, Plus, RefreshCw, Trash2 } from 'lucide';\n\n/**\n * Icon only\n *\n * `iconOnly` makes the face a square as tall as its size, with the glyph on the icon's own\n * step for it — every face, tone and state is the same button's. There is nothing on it to\n * read, so the name is written on the button: a tooltip with `pctTooltipAs=\"name\"` shows it\n * and speaks it, `aria-label` only speaks it. Press refresh to watch the spinner take the\n * glyph's place.\n */\n@Component({\n  selector: 'demo-button-icon-only',\n  imports: [PctButton, PctIcon, PctTooltip],\n  providers: [\n    providePctIcons(\n      svgIcons({\n        pencil: Pencil,\n        copy: Copy,\n        download: Download,\n        trash: Trash2,\n        plus: Plus,\n        refresh: RefreshCw,\n      }),\n    ),\n  ],\n  styles: `\n    :host {\n      display: flex;\n      flex-wrap: wrap;\n      align-items: center;\n      gap: 8px;\n    }\n    .gap {\n      inline-size: 16px;\n    }\n  `,\n  template: `\n    <button\n      pctButton\n      iconOnly\n      variant=\"ghost\"\n      pctTooltip=\"Edit\"\n      pctTooltipAs=\"name\"\n    >\n      <pct-icon icon=\"pencil\" />\n    </button>\n    <button\n      pctButton\n      iconOnly\n      variant=\"ghost\"\n      pctTooltip=\"Duplicate\"\n      pctTooltipAs=\"name\"\n    >\n      <pct-icon icon=\"copy\" />\n    </button>\n    <button\n      pctButton\n      iconOnly\n      variant=\"ghost\"\n      pctTooltip=\"Download\"\n      pctTooltipAs=\"name\"\n    >\n      <pct-icon icon=\"download\" />\n    </button>\n    <button\n      pctButton\n      iconOnly\n      variant=\"ghost\"\n      tone=\"danger\"\n      pctTooltip=\"Delete\"\n      pctTooltipAs=\"name\"\n    >\n      <pct-icon icon=\"trash\" />\n    </button>\n    <span class=\"gap\"></span>\n    <button\n      pctButton\n      iconOnly\n      variant=\"outline\"\n      aria-label=\"Refresh\"\n      [loading]=\"refreshing()\"\n      (click)=\"refresh()\"\n    >\n      <pct-icon icon=\"refresh\" />\n    </button>\n    <button pctButton iconOnly size=\"lg\" aria-label=\"New document\">\n      <pct-icon icon=\"plus\" />\n    </button>\n  `,\n})\nexport class ButtonIconOnlyDemo {\n  protected readonly refreshing = signal(false);\n\n  protected refresh(): void {\n    this.refreshing.set(true);\n    setTimeout(() => this.refreshing.set(false), 1500);\n  }\n}"
        },
        {
          "key": "toolbar",
          "title": "In a toolbar",
          "code": "import { Component } from '@angular/core';\nimport { PctButton } from '@pacit/components/button';\n\n/**\n * In a toolbar\n *\n * A row of actions is the consumer's own flex box — `pct-stack` stacks, it does not row.\n * The primary action goes last, the destructive one is kept at arm's length by the gap\n * that grows between them.\n */\n@Component({\n  selector: 'demo-button-toolbar',\n  imports: [PctButton],\n  styles: `\n    :host {\n      display: flex;\n      align-items: center;\n      gap: 8px;\n    }\n    .grow {\n      flex: 1;\n    }\n  `,\n  template: `\n    <button pctButton variant=\"ghost\">Delete draft</button>\n    <span class=\"grow\"></span>\n    <button pctButton variant=\"outline\">Cancel</button>\n    <button pctButton>Publish</button>\n  `,\n})\nexport class ButtonToolbarDemo {}"
        },
        {
          "key": "form",
          "title": "In a form",
          "code": "import { Component, signal } from '@angular/core';\nimport { FormField, form, required } from '@angular/forms/signals';\nimport { PctButton } from '@pacit/components/button';\nimport { PctField, PctText } from '@pacit/components/field';\n\n/**\n * In a form\n *\n * The submit button is a native `type=\"submit\"`: the signal form's validity drives\n * `disabled`, and the row heights match because the field and the button read the same\n * size token. Clear the name to watch the button follow.\n */\n@Component({\n  selector: 'demo-button-form',\n  imports: [FormField, PctButton, PctField, PctText],\n  styles: `\n    form {\n      display: grid;\n      gap: 12px;\n      max-width: 360px;\n    }\n  `,\n  template: `\n    <form (submit)=\"create($event)\">\n      <pct-field label=\"Workspace name\" hint=\"Letters, digits and dashes.\">\n        <input pctText [formField]=\"workspaceForm.name\" />\n      </pct-field>\n      <button pctButton type=\"submit\" [disabled]=\"workspaceForm().invalid()\">\n        {{ created() ? 'Created' : 'Create workspace' }}\n      </button>\n    </form>\n  `,\n})\nexport class ButtonFormDemo {\n  protected readonly workspace = signal({ name: 'northwind' });\n  protected readonly workspaceForm = form(this.workspace, (path) => {\n    required(path.name, { message: 'Every workspace needs a name' });\n  });\n  protected readonly created = signal(false);\n\n  protected create(event: Event): void {\n    event.preventDefault();\n    this.created.set(true);\n    setTimeout(() => this.created.set(false), 1500);\n  }\n}"
        }
      ],
      "api": [
        {
          "name": "PctButton",
          "kind": "component",
          "selector": "button[pctButton], a[pctButton]",
          "description": "Button. An attribute selector on a native <button> — or on a native <a>, when the control navigates — so semantics, keyboard handling and focus work natively (req-a11y-built-in). The paint is the same on both; what differs is the element, and the element is the consumer's (0071).",
          "members": [
            {
              "name": "variant",
              "kind": "input",
              "required": false,
              "type": "PctButtonVariant",
              "default": "'solid'",
              "description": "Picks the face. All five paint the same element; hero adds the drifting gradient and freezes it under reduced motion.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "tone",
              "kind": "input",
              "required": false,
              "type": "PctTone | null",
              "default": "null",
              "description": "Which of the skin's four families the face paints from — danger, warning, success, info — or null, the default, which is the brand. The names are shared with every other component that wears a tone rather than invented here ({@link PctTone}, 0076), and there is no primary member because primary is what a button with no tone already is.",
              "since": "0.2.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "size",
              "kind": "input",
              "required": false,
              "type": "PctButtonSize",
              "default": "config.defaultSize",
              "description": "Height 28 / 36 / 44 px — the axis every field shares, so rows line up. From providePctConfig by default (req-api-config).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "iconOnly",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "The face for one glyph and no words: a square as tall as the size, the glyph drawn on the icon's own step for that size, and 24 px under both axes whatever a skin does to the height. Geometry only — every face, tone and state above is the same button.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "disabled",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Blocks the click and greys the face; the grey is written to survive forced colors. On a link it is aria-disabled and a refused navigation — the platform has no disabled link.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "loading",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Shows the spinner in the face's own colour and sets aria-busy. Does not block the click — pair it with disabled when it should.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-button",
              "note": ""
            },
            {
              "attribute": "data-pct-variant",
              "kind": "attribute",
              "bound": "variant()",
              "note": ""
            },
            {
              "attribute": "data-pct-tone",
              "kind": "attribute",
              "bound": "appliedTone()",
              "note": "Absent when there is no tone, and absent on the hero face whatever was asked for: an attribute the stylesheet must not answer is better not written than written and ignored."
            },
            {
              "attribute": "data-pct-size",
              "kind": "attribute",
              "bound": "size()",
              "note": ""
            },
            {
              "attribute": "data-pct-icon-only",
              "kind": "attribute",
              "bound": "iconOnly() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-loading",
              "kind": "attribute",
              "bound": "loading() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-disabled",
              "kind": "attribute",
              "bound": "isDisabled() ? \"\" : null",
              "note": "The face's own hook, written on both elements, because only one of them has a <code>disabled</code> attribute to key on and the stylesheet should not have to know which."
            },
            {
              "attribute": "disabled",
              "kind": "attribute",
              "bound": "isButton && isDisabled() ? \"\" : null",
              "note": "The platform's own refusal, where the platform has one: on a <code>&lt;button&gt;</code> the attribute blocks the click, drops the element out of the tab order and says &quot;disabled&quot; in the accessibility tree without anything being written for it."
            },
            {
              "attribute": "aria-disabled",
              "kind": "attribute",
              "bound": "!isButton && isDisabled() ? \"true\" : null",
              "note": "And where it has none. A link cannot be disabled — <code>aria-disabled</code> says the state, <code>onClick</code> refuses the navigation, and the element stays focusable on purpose: a control a reader cannot reach cannot tell them why it does nothing."
            },
            {
              "attribute": "aria-busy",
              "kind": "attribute",
              "bound": "loading() ? \"true\" : null",
              "note": "Only while the button actually works. <code>aria-busy=&quot;false&quot;</code> is the default value, so writing it out adds nothing and stands in the accessibility tree of every button on the page."
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctButton",
          "kind": "component",
          "selector": "button[pctButton], a[pctButton]",
          "detail": null,
          "description": "Button. An attribute selector on a native <button> — or on a native <a>, when the control navigates — so semantics, keyboard handling and focus work natively (req-a11y-built-in). The paint is the same on both; what differs is the element, and the element is the consumer's (0071).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctButtonVariant",
          "kind": "type",
          "selector": null,
          "detail": "'solid' | 'outline' | 'ghost' | 'soft' | 'hero'",
          "description": "How much of the page's colour a button takes: solid, outline, ghost, soft, or hero — the one loud face, with the gradient that drifts.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctButtonSize",
          "kind": "type",
          "selector": null,
          "detail": "PctSize",
          "description": "The size of a button: the library's own scale.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "label",
          "description": "the span around the projected content"
        },
        {
          "name": "spinner",
          "description": "the loading ring, present only while loading"
        }
      ],
      "tokens": [
        {
          "name": "--pct-button-bg",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Background of the solid face."
        },
        {
          "name": "--pct-button-bg-active",
          "type": "color",
          "ref": "{pct.primary-active}",
          "light": "#1e40af",
          "dark": "#bfdbfe",
          "description": "Solid face background while pressed."
        },
        {
          "name": "--pct-button-bg-disabled",
          "type": "color",
          "ref": "{pct.surface-disabled}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of any face while disabled."
        },
        {
          "name": "--pct-button-bg-hover",
          "type": "color",
          "ref": "{pct.primary-hover}",
          "light": "#1d4ed8",
          "dark": "#93c5fd",
          "description": "Solid face background on hover."
        },
        {
          "name": "--pct-button-border",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge of the solid and outline faces."
        },
        {
          "name": "--pct-button-border-disabled",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge while disabled."
        },
        {
          "name": "--pct-button-fg",
          "type": "color",
          "ref": "{pct.on-primary}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Label and spinner colour on the solid face."
        },
        {
          "name": "--pct-button-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Label colour while disabled — measured on purpose, though SC 1.4.3 exempts it."
        },
        {
          "name": "--pct-button-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Label size at size md."
        },
        {
          "name": "--pct-button-font-size-lg",
          "type": "dimension",
          "ref": "{pct.font.size.lg}",
          "light": "16px",
          "dark": "16px",
          "description": "Label size at size lg."
        },
        {
          "name": "--pct-button-font-size-sm",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Label size at size sm."
        },
        {
          "name": "--pct-button-font-weight",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Label weight, every face."
        },
        {
          "name": "--pct-button-height",
          "type": "dimension",
          "ref": "{pct.control.height.md}",
          "light": "36px",
          "dark": "36px",
          "description": "Height at size md — the control axis every field shares, so rows line up."
        },
        {
          "name": "--pct-button-height-lg",
          "type": "dimension",
          "ref": "{pct.control.height.lg}",
          "light": "44px",
          "dark": "44px",
          "description": "Height at size lg."
        },
        {
          "name": "--pct-button-height-sm",
          "type": "dimension",
          "ref": "{pct.control.height.sm}",
          "light": "28px",
          "dark": "28px",
          "description": "Height at size sm."
        },
        {
          "name": "--pct-button-icon-size",
          "type": "dimension",
          "ref": null,
          "light": "20px",
          "dark": "20px",
          "description": "Glyph size on the icon-only face at size md."
        },
        {
          "name": "--pct-button-icon-size-lg",
          "type": "dimension",
          "ref": null,
          "light": "24px",
          "dark": "24px",
          "description": "Glyph size on the icon-only face at size lg."
        },
        {
          "name": "--pct-button-icon-size-sm",
          "type": "dimension",
          "ref": null,
          "light": "16px",
          "dark": "16px",
          "description": "Glyph size on the icon-only face at size sm."
        },
        {
          "name": "--pct-button-padding-x",
          "type": "dimension",
          "ref": "{pct.space.5}",
          "light": "16px",
          "dark": "16px",
          "description": "Horizontal padding at size md."
        },
        {
          "name": "--pct-button-padding-x-lg",
          "type": "dimension",
          "ref": "{pct.space.5}",
          "light": "16px",
          "dark": "16px",
          "description": "Horizontal padding at size lg."
        },
        {
          "name": "--pct-button-padding-x-sm",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding at size sm."
        },
        {
          "name": "--pct-button-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of every face."
        },
        {
          "name": "--pct-button-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Smallest height any face draws, and the smallest width of the square."
        }
      ],
      "texts": [],
      "keyboard": "None of its own. `Enter` and `Space` come from the native `<button>`; a link answers `Enter`\nalone, because that is what a link does. That is\n[`req-api-platform`](../requirements/api.md#req-api-platform) at work, not an oversight — and\nit is also why the two elements differ here rather than being made to agree.",
      "decisions": [
        {
          "id": "0001",
          "title": "Template and styles in separate files"
        },
        {
          "id": "0004",
          "title": "Height stated outright, not derived from padding"
        },
        {
          "id": "0008",
          "title": "The motion axis in tokens"
        },
        {
          "id": "0058",
          "title": "The hero face is paint, and a gradient is three contrast checks"
        },
        {
          "id": "0071",
          "title": "A link in button's clothes is a link"
        },
        {
          "id": "0076",
          "title": "A tone is two channels and four names, settled once"
        },
        {
          "id": "0082",
          "title": "A tone on a button is a face, and the label is what speaks"
        },
        {
          "id": "0085",
          "title": "An icon-only button is a face, and its name is written on it"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 92.54,
          "killed": 124,
          "survived": 10,
          "mutants": 134,
          "files": 1
        },
        "e2e": {
          "cases": 22,
          "spec": "apps/sandbox-e2e/src/button.spec.ts"
        },
        "pairs": 45,
        "baselines": 9,
        "cost": {
          "elements": 13,
          "depth": 2,
          "listeners": 0,
          "renders": 1,
          "micros": 5828,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "menu",
      "classes": [
        "PctMenu"
      ],
      "role": "a list of commands, walked by the keyboard and chosen from",
      "category": "Actions & navigation",
      "status": "released",
      "entrypoint": "@pacit/components/menu",
      "selectors": [
        "pct-menu",
        "button[pctMenuItem]",
        "[pctMenuTrigger]"
      ],
      "harnesses": [
        "PctMenuHarness",
        "PctMenuItemHarness",
        "PctMenuTriggerHarness"
      ],
      "docs": "/components/menu",
      "summary": "A list of commands to choose from, walked by the keyboard and closed by the choice.",
      "usage": {
        "code": "<button pctButton [pctMenuTrigger]=\"actions\">Actions</button>\n<pct-menu #actions>\n  <button pctMenuItem (click)=\"rename()\">Rename</button>\n</pct-menu>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "placement",
          "title": "Placement, and why it closed",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctButton } from '@pacit/components/button';\nimport {\n  PctMenu,\n  PctMenuCloseReason,\n  PctMenuItem,\n  PctMenuTrigger,\n} from '@pacit/components/menu';\n\n/**\n * Placement, and why it closed\n *\n * `placement` asks for a side; the panel takes it when there is room and flips when there\n * is not. `(closed)` reports the reason — an item, the trigger, Escape, a click outside —\n * so a consumer can tell a choice from a dismissal.\n */\n@Component({\n  selector: 'demo-menu-placement',\n  imports: [PctButton, PctMenu, PctMenuItem, PctMenuTrigger],\n  styles: ':host { display: grid; gap: 12px; justify-items: start; }',\n  template: `\n    <button pctButton variant=\"outline\" [pctMenuTrigger]=\"sort\">Sort by</button>\n    <pct-menu #sort placement=\"end\" (closed)=\"reason.set($event)\">\n      <button pctMenuItem>Newest first</button>\n      <button pctMenuItem>Oldest first</button>\n      <button pctMenuItem>Most discussed</button>\n    </pct-menu>\n    <p>Last closed by: {{ reason() ?? 'not opened yet' }}</p>\n  `,\n})\nexport class MenuPlacementDemo {\n  readonly reason = signal<PctMenuCloseReason | null>(null);\n}"
        }
      ],
      "api": [
        {
          "name": "PctMenu",
          "kind": "component",
          "selector": "pct-menu",
          "description": "A menu: a list of commands on a panel, walked by the keyboard and chosen from.",
          "members": [
            {
              "name": "open",
              "kind": "model",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the panel is up. A model, because both directions are ordinary: an application opens it, and the menu closes itself on a choice, on Escape, on a press outside, on the trigger and on Tab walking out — every one of which is a panel on a layer getting out of the page's way. An inline menu has nowhere to get out of, and writes this from nobody but the application (inline).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name, for a menu whose trigger does not already say it. Usually neither is needed: a menu with no name of its own is named by the control that opened it, which is what the APG's own example does and what makes \"Actions menu\" come out of the two words already on the screen.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabelledby",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "As ariaLabel, for a name that already stands somewhere on the page.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "placement",
              "kind": "input",
              "required": false,
              "type": "PctPlacement",
              "default": "'bottom'",
              "description": "Which side of the control it opens on — logical, so end is the right in an English page and the left in an Arabic one. bottom is the menu button's side and end the submenu's; the window has the last word either way (pctPlacementPositions). Inline it is read by nobody: a panel that is not positioned has no side to be on.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "inline",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Renders the panel in the host, where the component was written, instead of on a layer over the page — a column of commands docked into the page rather than one lifted off it.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "closed",
              "kind": "output",
              "required": false,
              "type": "PctMenuCloseReason",
              "default": null,
              "description": "Why it closed. See PctMenuCloseReason.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-menu",
              "note": ""
            },
            {
              "attribute": "style",
              "kind": "static",
              "bound": "display: contents",
              "note": "The host renders nothing — everything it draws lives in the overlay. It stays in the tree because it is where the panel's severed properties are read from."
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctMenuItemApi",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What a menu needs to know about one of its items, and nothing more. Declared HERE, beside the menu, so that the item can provide it while importing the menu — the other direction (contentChildren(PctMenuItem)) would close the import cycle. It is pct-radio's channel one component over, and for the same reason.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PCT_MENU_ITEM",
          "kind": "token",
          "selector": null,
          "detail": null,
          "description": "The channel through which an item announces itself to the menu it stands in.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctMenu",
          "kind": "component",
          "selector": "pct-menu",
          "detail": null,
          "description": "A menu: a list of commands on a panel, walked by the keyboard and chosen from.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctMenuItem",
          "kind": "component",
          "selector": "button[pctMenuItem]",
          "detail": null,
          "description": "One command on a menu: role=\"menuitem\", focusable by the walk and by nothing else.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctMenuTrigger",
          "kind": "directive",
          "selector": "[pctMenuTrigger]",
          "detail": null,
          "description": "The control a menu hangs off: it opens the panel and says so about itself.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctMenuCloseReason",
          "kind": "type",
          "selector": null,
          "detail": "'item' | 'trigger' | 'escape' | 'outside' | 'away' | 'api'",
          "description": "Why a menu closed. open says *that* it is shut and nothing else, and the question an application asks about a menu it did not close itself — was something chosen, or did the user walk away — is answered by this and by nothing in the boolean.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctMenuOpenIntent",
          "kind": "type",
          "selector": null,
          "detail": "'first' | 'last' | 'none'",
          "description": "Where focus lands when a panel comes up — the opening's half of PctMenuCloseReason.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "panel",
          "description": "the floating list of commands"
        },
        {
          "name": "item",
          "description": "one command"
        }
      ],
      "tokens": [
        {
          "name": "--pct-menu-item-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of an item."
        },
        {
          "name": "--pct-menu-item-bg-active",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the item the keyboard or the pointer is on."
        },
        {
          "name": "--pct-menu-item-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of an item."
        },
        {
          "name": "--pct-menu-item-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of an item while disabled."
        },
        {
          "name": "--pct-menu-item-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of an item."
        },
        {
          "name": "--pct-menu-item-gap",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Gap between an item's icon and its text."
        },
        {
          "name": "--pct-menu-item-padding-x",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of an item."
        },
        {
          "name": "--pct-menu-item-padding-y",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Vertical padding of an item."
        },
        {
          "name": "--pct-menu-item-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Touch-target floor of an item."
        },
        {
          "name": "--pct-menu-panel-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the panel."
        },
        {
          "name": "--pct-menu-panel-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the panel."
        },
        {
          "name": "--pct-menu-panel-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the panel."
        },
        {
          "name": "--pct-menu-panel-max-height",
          "type": "dimension",
          "ref": null,
          "light": "320px",
          "dark": "320px",
          "description": "Maximum height of the panel."
        },
        {
          "name": "--pct-menu-panel-padding-y",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Vertical padding of the panel."
        },
        {
          "name": "--pct-menu-panel-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the panel."
        },
        {
          "name": "--pct-menu-panel-shadow",
          "type": "shadow",
          "ref": "{pct.shadow-panel}",
          "light": "0 8px 24px rgba(15, 23, 42, 0.14)",
          "dark": "0 8px 24px rgba(0, 0, 0, 0.55)",
          "description": "Shadow of the panel."
        }
      ],
      "texts": [],
      "keyboard": "| key                                  | effect                                                                                                                                                                                                   | test                                                                          |\n| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |\n| `ArrowDown` / `ArrowUp`              | the next/previous command, skipping what is disabled, coming round at the ends                                                                                                                           | `apps/sandbox-e2e/src/menu.spec.ts`                                           |\n| `Home` / `End`                       | the first/last command                                                                                                                                                                                   | `apps/sandbox-e2e/src/menu.spec.ts`                                           |\n| a letter                             | the first command whose text starts with what has been typed; a second letter narrows it                                                                                                                 | `apps/sandbox-e2e/src/menu.spec.ts`                                           |\n| `Enter` / `Space`                    | runs the command — **the platform's**, because a row is a `<button>`, and nothing here reads it                                                                                                          | `apps/sandbox-e2e/src/menu.spec.ts`                                           |\n| `Escape`                             | closes one level, focus back to what opened it: the trigger, or the row a submenu hangs on                                                                                                               | `apps/sandbox-e2e/src/menu.spec.ts`                                           |\n| `Tab` / `Shift+Tab`                  | closes the whole tree and gives focus back to the trigger, for the page's order to carry on; **inline it is left alone** — the panel is already in that order, so Tab walks out of it and closes nothing | `apps/sandbox-e2e/src/menu.spec.ts` · `libs/components/menu/src/menu.spec.ts` |\n| `ArrowRight`                         | into the submenu of the active row — **logical**: in a right-to-left page it is `ArrowLeft`                                                                                                              | `apps/sandbox-e2e/src/menu.spec.ts`                                           |\n| `ArrowLeft`                          | out of a submenu, back onto the row it hangs on; in a root menu the key is left alone                                                                                                                    | `libs/components/menu/src/menu.spec.ts`                                       |\n| `ArrowDown`/`ArrowUp` on the trigger | opens it at the first / at the last command                                                                                                                                                              | `apps/sandbox-e2e/src/menu.spec.ts`                                           |\n\nThe panel itself is not in the Tab order (`tabindex=\"-1\"`); it takes focus only when the menu has\nno command to give it to, so that an empty menu is still somewhere Escape can be pressed.\n\n**Inline, one command is.** A panel drawn in the page has to be reachable from the page's own\norder, so exactly one row carries `tabindex=\"0\"` — the row the walk stands on, or the first\nreachable one before the first movement — and the arrows do the walking from there. It is the\nroving tabindex the APG writes this pattern with, which the layer deliberately does not have\n(0031), and the reason the two answers differ is the one thing the mode changes: over a layer Tab\nleaves the menu, inline it leaves the row.",
      "decisions": [
        {
          "id": "0066",
          "title": "A panel is a layer or a region, and the consumer says which"
        },
        {
          "id": "0032",
          "title": "A menu moves focus, a listbox points at it"
        },
        {
          "id": "0031",
          "title": "A panel's tab order belongs to its trigger"
        },
        {
          "id": "0024",
          "title": "The overlay layer carries what an overlay severs, and borrows the closing stack"
        },
        {
          "id": "0025",
          "title": "A panel says whether it takes focus, and the rest of the focus layer waits for a consumer"
        },
        {
          "id": "0013",
          "title": "No headless core / skin split"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 81.16,
          "killed": 280,
          "survived": 56,
          "mutants": 345,
          "files": 1
        },
        "e2e": {
          "cases": 21,
          "spec": "apps/sandbox-e2e/src/menu.spec.ts"
        },
        "pairs": 5,
        "baselines": 1,
        "cost": {
          "elements": 4,
          "depth": 2,
          "listeners": 8,
          "renders": 1,
          "micros": 6253,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "tabs",
      "classes": [
        "PctTabs"
      ],
      "role": "one section of a page showing at a time",
      "category": "Actions & navigation",
      "status": "released",
      "entrypoint": "@pacit/components/tabs",
      "selectors": [
        "pct-tabs",
        "pct-tab"
      ],
      "harnesses": [
        "PctTabsHarness",
        "PctTabHarness"
      ],
      "docs": "/components/tabs",
      "summary": "One section of a page showing at a time, in two faces and two layouts.",
      "usage": {
        "code": "<pct-tabs [(value)]=\"section\" ariaLabel=\"Account settings\">\n  <pct-tab value=\"general\" label=\"General\">Name, avatar, language.</pct-tab>\n  <pct-tab value=\"network\" label=\"Network\">Proxies and timeouts.</pct-tab>\n</pct-tabs>\n\n<!-- a segmented switch, and a strip beside its panel -->\n<pct-tabs variant=\"segmented\" [(value)]=\"period\" ariaLabel=\"Period\">\n  <pct-tab value=\"day\" label=\"Day\">Today, hour by hour.</pct-tab>\n  <pct-tab value=\"week\" label=\"Week\">Seven days side by side.</pct-tab>\n</pct-tabs>\n\n<pct-tabs orientation=\"vertical\" [(value)]=\"section\" ariaLabel=\"Project\">\n  <pct-tab value=\"overview\" label=\"Overview\">What it is, in one paragraph.</pct-tab>\n  <pct-tab value=\"members\" label=\"Members\">Who can read and who can write.</pct-tab>\n</pct-tabs>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "vertical",
          "title": "A strip down the side",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctTab, PctTabs } from '@pacit/components/tabs';\n\n/**\n * A strip down the side\n *\n * `orientation=\"vertical\"` puts the strip beside its panel rather than above it: a column of\n * labels on one side, the chosen section on the other. The arrows that walk it become Up and\n * Down, and the chosen tab's edge moves to the inline side of the strip. Every distance is\n * logical, so in a right-to-left document the strip moves to the right and the panel to the\n * left with nothing to set.\n */\n@Component({\n  selector: 'demo-tabs-vertical',\n  imports: [PctTab, PctTabs],\n  styles: ':host { display: block; inline-size: min(100%, 32rem); }',\n  template: `\n    <pct-tabs [(value)]=\"section\" orientation=\"vertical\" ariaLabel=\"Project\">\n      <pct-tab value=\"overview\" label=\"Overview\">\n        <p>What the project is, in one paragraph.</p>\n      </pct-tab>\n      <pct-tab value=\"members\" label=\"Members\">\n        <p>Who can read it and who can write to it.</p>\n      </pct-tab>\n      <pct-tab value=\"danger\" label=\"Danger zone\">\n        <p>Archiving and deleting live here, behind a second confirmation.</p>\n      </pct-tab>\n    </pct-tabs>\n  `,\n})\nexport class TabsVerticalDemo {\n  readonly section = signal('overview');\n}"
        },
        {
          "key": "manual",
          "title": "Manual activation",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctTab, PctTabs } from '@pacit/components/tabs';\n\n/**\n * Manual activation\n *\n * By default the arrows choose as they move. With `activation=\"manual\"` they only move the\n * focus, and Enter or Space makes the choice — the APG's own second mode, for panels that\n * are expensive to show.\n */\n@Component({\n  selector: 'demo-tabs-manual',\n  imports: [PctTab, PctTabs],\n  styles: ':host { display: block; inline-size: min(100%, 32rem); }',\n  template: `\n    <pct-tabs [(value)]=\"report\" activation=\"manual\" ariaLabel=\"Reports\">\n      <pct-tab value=\"daily\" label=\"Daily\">\n        <p>Yesterday, hour by hour.</p>\n      </pct-tab>\n      <pct-tab value=\"weekly\" label=\"Weekly\">\n        <p>The last seven days, day by day.</p>\n      </pct-tab>\n      <pct-tab value=\"yearly\" label=\"Yearly\">\n        <p>Twelve months on one line.</p>\n      </pct-tab>\n    </pct-tabs>\n  `,\n})\nexport class TabsManualDemo {\n  readonly report = signal('daily');\n}"
        },
        {
          "key": "segmented",
          "title": "A segmented switch",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctTab, PctTabs } from '@pacit/components/tabs';\n\n/**\n * A segmented switch\n *\n * `variant=\"segmented\"` swaps the rail for a recessed track and raises the chosen tab out\n * of it. This page's own Preview / Code control is this one input — before the variant\n * existed it took a dozen token overrides to undo the rail plus two rules reaching through\n * the parts, because the track and the chosen fill had no token to set.\n */\n@Component({\n  selector: 'demo-tabs-segmented',\n  imports: [PctTab, PctTabs],\n  styles: ':host { display: block; inline-size: min(100%, 32rem); }',\n  template: `\n    <pct-tabs variant=\"segmented\" [(value)]=\"period\" ariaLabel=\"Period\">\n      <pct-tab value=\"day\" label=\"Day\">\n        <p>Today, hour by hour.</p>\n      </pct-tab>\n      <pct-tab value=\"week\" label=\"Week\">\n        <p>Seven days side by side.</p>\n      </pct-tab>\n      <pct-tab value=\"month\" label=\"Month\">\n        <p>The month on one grid.</p>\n      </pct-tab>\n    </pct-tabs>\n  `,\n})\nexport class TabsSegmentedDemo {\n  readonly period = signal('week');\n}"
        }
      ],
      "api": [
        {
          "name": "PctTabs",
          "kind": "component",
          "selector": "pct-tabs",
          "description": "Tabs: one section of a page showing at a time, chosen from a strip of labels.",
          "members": [
            {
              "name": "value",
              "kind": "model",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "Which panel is showing, by the value of a <pct-tab>.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "orientation",
              "kind": "input",
              "required": false,
              "type": "PctTabsOrientation",
              "default": "'horizontal'",
              "description": "Which axis the strip runs along; the arrows follow it.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "activation",
              "kind": "input",
              "required": false,
              "type": "PctTabsActivation",
              "default": "'automatic'",
              "description": "Whether a walk over the strip also chooses. See PctTabsActivation.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "variant",
              "kind": "input",
              "required": false,
              "type": "PctTabsVariant",
              "default": "'underline'",
              "description": "Which face the strip wears; the keyboard and the semantics are the same either way. See PctTabsVariant.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The strip's accessible name. An INPUT rather than an aria-label on the tag: the role sits on a <div> inside this template, the host carries no role at all, and an ARIA name on a roleless element is ignored (req-a11y-built-in).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabelledby",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "As ariaLabel, for a name that already stands somewhere on the page.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-tabs",
              "note": ""
            },
            {
              "attribute": "data-pct-orientation",
              "kind": "attribute",
              "bound": "orientation()",
              "note": ""
            },
            {
              "attribute": "data-pct-variant",
              "kind": "attribute",
              "bound": "variant()",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctTabApi",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What a strip needs to know about one of its panels, and nothing more. Declared HERE, beside the strip, so that the panel can provide it while importing the strip — the other direction (contentChildren(PctTab)) would close the import cycle. It is pct-menu's channel one component over, and for the same reason.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PCT_TAB",
          "kind": "token",
          "selector": null,
          "detail": null,
          "description": "The channel through which a panel announces itself to the strip it stands in.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctTabsApi",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What a panel needs to know about the strip it stands in: which value is showing, and how to ask for its own. The second half exists for one caller — a panel the browser has just found text in (0045).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PCT_TABS",
          "kind": "token",
          "selector": null,
          "detail": null,
          "description": "The channel through which a strip offers itself to the panels below it.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctTabs",
          "kind": "component",
          "selector": "pct-tabs",
          "detail": null,
          "description": "Tabs: one section of a page showing at a time, chosen from a strip of labels.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctTab",
          "kind": "component",
          "selector": "pct-tab",
          "detail": null,
          "description": "One section of a <pct-tabs>: role=\"tabpanel\", holding its content where the consumer wrote it, and handing the strip above only the label to draw.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctTabsOrientation",
          "kind": "type",
          "selector": null,
          "detail": "'horizontal' | 'vertical'",
          "description": "Which axis the strip runs along. It is aria-orientation on the tablist and the axis the arrow keys walk — one input for both, because a strip drawn down the side and walked with ArrowRight is the defect the attribute exists to describe.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctTabsActivation",
          "kind": "type",
          "selector": null,
          "detail": "'automatic' | 'manual'",
          "description": "Whether moving the keyboard over the strip also chooses.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctTabsVariant",
          "kind": "type",
          "selector": null,
          "detail": "'underline' | 'segmented'",
          "description": "Which face the strip wears.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "panel",
          "description": "a tab's section"
        },
        {
          "name": "list",
          "description": "the strip of tabs"
        },
        {
          "name": "tab",
          "description": "one tab button"
        }
      ],
      "tokens": [
        {
          "name": "--pct-tabs-list-bg",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Ground of the strip, painted by the segmented face."
        },
        {
          "name": "--pct-tabs-list-border",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Colour of the strip's rail."
        },
        {
          "name": "--pct-tabs-list-gap",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Gap between tabs."
        },
        {
          "name": "--pct-tabs-list-inset",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Inset of the track around the segments."
        },
        {
          "name": "--pct-tabs-panel-gap",
          "type": "dimension",
          "ref": "{pct.space.5}",
          "light": "16px",
          "dark": "16px",
          "description": "Distance between a side strip and its panel."
        },
        {
          "name": "--pct-tabs-panel-padding-y",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Vertical padding of the panel."
        },
        {
          "name": "--pct-tabs-tab-bg-hover",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of a tab on hover."
        },
        {
          "name": "--pct-tabs-tab-bg-selected",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the chosen tab in the segmented face."
        },
        {
          "name": "--pct-tabs-tab-border-selected",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Colour of the chosen tab's edge."
        },
        {
          "name": "--pct-tabs-tab-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of a tab."
        },
        {
          "name": "--pct-tabs-tab-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of a tab while disabled."
        },
        {
          "name": "--pct-tabs-tab-fg-hover",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of a tab on hover."
        },
        {
          "name": "--pct-tabs-tab-fg-selected",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of a tab when selected."
        },
        {
          "name": "--pct-tabs-tab-font-weight",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of a tab."
        },
        {
          "name": "--pct-tabs-tab-padding-x",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of a tab."
        },
        {
          "name": "--pct-tabs-tab-padding-y",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Vertical padding of a tab."
        },
        {
          "name": "--pct-tabs-tab-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of a tab."
        },
        {
          "name": "--pct-tabs-tab-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Touch-target floor of a tab."
        }
      ],
      "texts": [],
      "keyboard": "| key                        | effect                                                                             | test                                |\n| -------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------- |\n| `ArrowRight` / `ArrowLeft` | horizontal strip: the next / previous reachable tab, **swapped under `dir=\"rtl\"`** | `apps/sandbox-e2e/src/tabs.spec.ts` |\n| `ArrowDown` / `ArrowUp`    | vertical strip: the same, along the other axis                                     | `apps/sandbox-e2e/src/tabs.spec.ts` |\n| `Home` / `End`             | the first / last tab that can be reached                                           | `apps/sandbox-e2e/src/tabs.spec.ts` |\n| `Enter` / `Space`          | chooses the focused tab — the **platform's**, on a real `<button>`                 | `apps/sandbox-e2e/src/tabs.spec.ts` |\n| `Tab`                      | one stop for the whole strip, and the panel is the next one                        | `apps/sandbox-e2e/src/tabs.spec.ts` |\n\nThe arrows come round at both ends, which is the APG's own example. There is no typeahead: the\nshared walk in `core` switches it off when the source names no `label`, and a strip of a handful\nof visible words is not a list somebody types at. In `activation=\"automatic\"` a movement also\nchooses; in `\"manual\"` it only moves, and `Enter` or `Space` — which this component does not\nhandle, because a `<button>` already turns them into a click — does the choosing.",
      "decisions": [
        {
          "id": "0064",
          "title": "A face the consumer cannot reach without undoing ours is a variant"
        },
        {
          "id": "0045",
          "title": "A panel nobody chose is still text in the document"
        },
        {
          "id": "0026",
          "title": "One channel per politeness, and a message with a home announces from it"
        },
        {
          "id": "0032",
          "title": "A menu moves focus, a listbox points at it"
        },
        {
          "id": "0013",
          "title": "No headless core / skin split"
        },
        {
          "id": "0039",
          "title": "A state the platform publishes is not ours to write"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 93.5,
          "killed": 115,
          "survived": 7,
          "mutants": 123,
          "files": 1
        },
        "e2e": {
          "cases": 16,
          "spec": "apps/sandbox-e2e/src/tabs.spec.ts"
        },
        "pairs": 9,
        "baselines": 4,
        "cost": {
          "elements": 12,
          "depth": 4,
          "listeners": 12,
          "renders": 1,
          "micros": 6426,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "breadcrumb",
      "classes": [
        "PctBreadcrumb"
      ],
      "role": "the way here, told in links",
      "category": "Actions & navigation",
      "status": "released",
      "entrypoint": "@pacit/components/breadcrumb",
      "selectors": [
        "pct-breadcrumb",
        "pct-crumb",
        "a[pctCrumbLink]"
      ],
      "harnesses": [
        "PctBreadcrumbHarness",
        "PctCrumbHarness",
        "PctCrumbLinkHarness"
      ],
      "docs": "/components/breadcrumb",
      "summary": "The trail to the current page, told in links, with the page itself at the end of it.",
      "usage": {
        "code": "<pct-breadcrumb ariaLabel=\"You are here\">\n  <pct-crumb><a pctCrumbLink href=\"/\">Home</a></pct-crumb>\n  <pct-crumb><a pctCrumbLink href=\"/data\" aria-current=\"page\">Data</a></pct-crumb>\n</pct-breadcrumb>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "trail",
          "title": "A long way here",
          "code": "import { Component } from '@angular/core';\nimport {\n  PctBreadcrumb,\n  PctCrumb,\n  PctCrumbLink,\n} from '@pacit/components/breadcrumb';\n\n/**\n * A long way here\n *\n * The trail is an ordered list of links; only the last crumb carries `aria-current=\"page\"`,\n * and it is a link like the others so the current page is reachable by keyboard too.\n */\n@Component({\n  selector: 'demo-breadcrumb-trail',\n  imports: [PctBreadcrumb, PctCrumb, PctCrumbLink],\n  styles: ':host { display: block; }',\n  template: `\n    <pct-breadcrumb ariaLabel=\"You are here\">\n      <pct-crumb><a pctCrumbLink href=\"#org\">Northwind</a></pct-crumb>\n      <pct-crumb><a pctCrumbLink href=\"#projects\">Projects</a></pct-crumb>\n      <pct-crumb><a pctCrumbLink href=\"#design\">Design system</a></pct-crumb>\n      <pct-crumb><a pctCrumbLink href=\"#tokens\">Tokens</a></pct-crumb>\n      <pct-crumb>\n        <a pctCrumbLink href=\"#colour\" aria-current=\"page\">Colour</a>\n      </pct-crumb>\n    </pct-breadcrumb>\n  `,\n})\nexport class BreadcrumbTrailDemo {}"
        }
      ],
      "api": [
        {
          "name": "PctBreadcrumb",
          "kind": "component",
          "selector": "pct-breadcrumb",
          "description": "A breadcrumb: the way here, told in the platform's own links.",
          "members": [
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name of the landmark. Overrides texts().breadcrumbLabel, whose job is only to carry a sensible default in the application's language.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-breadcrumb",
              "note": ""
            },
            {
              "attribute": "role",
              "kind": "static",
              "bound": "navigation",
              "note": "A named <code>navigation</code> landmark — the pagination's reasoning one component over: the landmark is how a screen-reader user reaches the trail and steps over it, and it has to be named because two trails on one page are two landmarks."
            },
            {
              "attribute": "aria-label",
              "kind": "attribute",
              "bound": "ariaLabel() || texts().breadcrumbLabel",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctBreadcrumb",
          "kind": "component",
          "selector": "pct-breadcrumb",
          "detail": null,
          "description": "A breadcrumb: the way here, told in the platform's own links.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctCrumb",
          "kind": "component",
          "selector": "pct-crumb",
          "detail": null,
          "description": "One step of the trail: role=\"listitem\", and the separator drawn before its content.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctCrumbLink",
          "kind": "component",
          "selector": "a[pctCrumbLink]",
          "detail": null,
          "description": "The trail's link, standing on the consumer's own anchor.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "list",
          "description": "the ordered list of crumbs"
        },
        {
          "name": "separator",
          "description": "the mark between two crumbs"
        }
      ],
      "tokens": [
        {
          "name": "--pct-breadcrumb-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the breadcrumb."
        },
        {
          "name": "--pct-breadcrumb-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of the breadcrumb."
        },
        {
          "name": "--pct-breadcrumb-gap",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Gap between crumbs."
        },
        {
          "name": "--pct-breadcrumb-link-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of a link."
        },
        {
          "name": "--pct-breadcrumb-link-fg-hover",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of a link on hover."
        },
        {
          "name": "--pct-breadcrumb-link-font-weight-current",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of the link naming the current page."
        },
        {
          "name": "--pct-breadcrumb-link-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Touch-target floor of a link."
        },
        {
          "name": "--pct-breadcrumb-separator-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the separator."
        }
      ],
      "texts": [
        "breadcrumbLabel"
      ],
      "keyboard": "| key                 | effect                  | test                                                                                                        |\n| ------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------- |\n| `Tab` / `Shift+Tab` | moves between the steps | `apps/sandbox-e2e/src/breadcrumb.spec.ts` — \"every step is a real tab stop\"                                 |\n| `Enter`             | follows the link        | none — deliberately: the anchor navigates on its own, and testing it tests the browser (`req-api-platform`) |\n\nThere are no handlers to map: nothing here listens to a key.",
      "decisions": [],
      "evidence": {
        "mutation": {
          "score": 92,
          "killed": 23,
          "survived": 2,
          "mutants": 25,
          "files": 1
        },
        "e2e": {
          "cases": 6,
          "spec": "apps/sandbox-e2e/src/breadcrumb.spec.ts"
        },
        "pairs": 4,
        "baselines": 2,
        "cost": {
          "elements": 17,
          "depth": 6,
          "listeners": 0,
          "renders": 1,
          "micros": 7303,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "pagination",
      "classes": [
        "PctPagination"
      ],
      "role": "a control that owns the current page of a collection",
      "category": "Actions & navigation",
      "status": "released",
      "entrypoint": "@pacit/components/pagination",
      "selectors": [
        "pct-pagination"
      ],
      "harnesses": [
        "PctPaginationHarness"
      ],
      "docs": "/components/pagination",
      "summary": "A pager that owns the current page of a collection and says when the reader moves.",
      "usage": {
        "code": "<pct-pagination [(page)]=\"page\" [count]=\"20\" />",
        "lang": "html"
      },
      "examples": [
        {
          "key": "fold",
          "title": "A long list, folded",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctPagination } from '@pacit/components/pagination';\n\n/**\n * A long list, folded\n *\n * Two hundred pages do not fit on a line. `siblingCount` says how many neighbours the\n * current page keeps, `boundaryCount` how many pages stay pinned at each end, and the fold\n * between them is an ellipsis the keyboard never stops on.\n */\n@Component({\n  selector: 'demo-pagination-fold',\n  imports: [PctPagination],\n  styles: ':host { display: grid; gap: 12px; }',\n  template: `\n    <pct-pagination\n      [(page)]=\"page\"\n      [count]=\"200\"\n      [siblingCount]=\"2\"\n      [boundaryCount]=\"1\"\n      ariaLabel=\"Search results\"\n    />\n    <p>Page {{ page() }} of 200.</p>\n  `,\n})\nexport class PaginationFoldDemo {\n  readonly page = signal(37);\n}"
        },
        {
          "key": "sizes",
          "title": "Sizes",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctPagination } from '@pacit/components/pagination';\n\n/**\n * Sizes\n *\n * The same three heights as every control — 28, 36 and 44 px — so a pagination under a\n * table lines up with the table's own toolbar. The smallest still clears the touch-target\n * floor.\n */\n@Component({\n  selector: 'demo-pagination-sizes',\n  imports: [PctPagination],\n  styles: ':host { display: grid; gap: 12px; }',\n  template: `\n    <pct-pagination [(page)]=\"page\" [count]=\"9\" size=\"sm\" ariaLabel=\"Small\" />\n    <pct-pagination [(page)]=\"page\" [count]=\"9\" ariaLabel=\"Medium\" />\n    <pct-pagination [(page)]=\"page\" [count]=\"9\" size=\"lg\" ariaLabel=\"Large\" />\n  `,\n})\nexport class PaginationSizesDemo {\n  readonly page = signal(4);\n}"
        }
      ],
      "api": [
        {
          "name": "PctPagination",
          "kind": "component",
          "selector": "pct-pagination",
          "description": "Pagination: a control that owns the current page of a collection and emits when the user moves to another one.",
          "members": [
            {
              "name": "page",
              "kind": "model",
              "required": false,
              "type": "number",
              "default": "1",
              "description": "The current page, 1-based. A model, because both directions are ordinary: an application sets it, and a press on a page button writes it back.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "count",
              "kind": "input",
              "required": true,
              "type": "number",
              "default": null,
              "description": "How many pages there are. Not how many items — see the class note. A count below 1 draws an empty landmark; a count of 1 draws a single current page with both steppers disabled.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "siblingCount",
              "kind": "input",
              "required": false,
              "type": "number",
              "default": "1",
              "description": "How many page numbers to keep on each side of the current one before folding.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "boundaryCount",
              "kind": "input",
              "required": false,
              "type": "number",
              "default": "1",
              "description": "How many page numbers to keep pinned at each end of the strip.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "disabled",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the whole control is inert — every button carries disabled.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name of the landmark. Overrides texts().paginationLabel, whose job is only to carry a sensible default in the application's language.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "controls",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The id of the region whose content this pager moves through, written as aria-controls on each page button. Optional: when it is empty the attribute is not written, because an aria-controls that resolves to nothing is worse than none.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "size",
              "kind": "input",
              "required": false,
              "type": "PctSize",
              "default": "config.defaultSize",
              "description": "Size; taken from the global configuration by default (req-api-config).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-pagination",
              "note": ""
            },
            {
              "attribute": "role",
              "kind": "static",
              "bound": "navigation",
              "note": "A <code>navigation</code> landmark and not <code>role=&quot;group&quot;</code>: moving between pages of a collection is navigation of content whether or not a page boundary is a URL, and a landmark is how a screen-reader user reaches the pager and steps over it. It has to be named — two pagers on one page (above and below a table) are two landmarks, and <code>ariaLabel</code> is what tells them apart."
            },
            {
              "attribute": "aria-label",
              "kind": "attribute",
              "bound": "ariaLabel() || texts().paginationLabel",
              "note": ""
            },
            {
              "attribute": "data-pct-size",
              "kind": "attribute",
              "bound": "size()",
              "note": ""
            },
            {
              "attribute": "data-pct-disabled",
              "kind": "attribute",
              "bound": "disabled() ? \"\" : null",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctPagination",
          "kind": "component",
          "selector": "pct-pagination",
          "detail": null,
          "description": "Pagination: a control that owns the current page of a collection and emits when the user moves to another one.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctPaginationItem",
          "kind": "type",
          "selector": null,
          "detail": "number | 'ellipsis'",
          "description": "One entry in the strip the component draws: either a page number to offer, or a gap where a run of pages has been folded away.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "ellipsis",
          "description": "the fold between distant pages"
        },
        {
          "name": "list",
          "description": "the list of page buttons"
        },
        {
          "name": "next",
          "description": "the button to the page after"
        },
        {
          "name": "page",
          "description": "one page button"
        },
        {
          "name": "previous",
          "description": "the button to the page before"
        }
      ],
      "tokens": [
        {
          "name": "--pct-pagination-item-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of an item."
        },
        {
          "name": "--pct-pagination-item-bg-hover",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of an item on hover."
        },
        {
          "name": "--pct-pagination-item-bg-selected",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Background of an item when selected."
        },
        {
          "name": "--pct-pagination-item-border-selected",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge colour of an item when selected."
        },
        {
          "name": "--pct-pagination-item-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of an item."
        },
        {
          "name": "--pct-pagination-item-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of an item while disabled."
        },
        {
          "name": "--pct-pagination-item-fg-hover",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of an item on hover."
        },
        {
          "name": "--pct-pagination-item-fg-selected",
          "type": "color",
          "ref": "{pct.on-primary}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Text colour of an item when selected."
        },
        {
          "name": "--pct-pagination-item-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of an item."
        },
        {
          "name": "--pct-pagination-item-font-size-lg",
          "type": "dimension",
          "ref": "{pct.font.size.lg}",
          "light": "16px",
          "dark": "16px",
          "description": "Font size of an item at size lg."
        },
        {
          "name": "--pct-pagination-item-font-size-sm",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of an item at size sm."
        },
        {
          "name": "--pct-pagination-item-font-weight-selected",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of an item when selected."
        },
        {
          "name": "--pct-pagination-item-height",
          "type": "dimension",
          "ref": "{pct.control.height.md}",
          "light": "36px",
          "dark": "36px",
          "description": "Height of an item."
        },
        {
          "name": "--pct-pagination-item-height-lg",
          "type": "dimension",
          "ref": "{pct.control.height.lg}",
          "light": "44px",
          "dark": "44px",
          "description": "Height of an item at size lg."
        },
        {
          "name": "--pct-pagination-item-height-sm",
          "type": "dimension",
          "ref": "{pct.control.height.sm}",
          "light": "28px",
          "dark": "28px",
          "description": "Height of an item at size sm."
        },
        {
          "name": "--pct-pagination-item-padding-x",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of an item."
        },
        {
          "name": "--pct-pagination-item-padding-x-lg",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of an item at size lg."
        },
        {
          "name": "--pct-pagination-item-padding-x-sm",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Horizontal padding of an item at size sm."
        },
        {
          "name": "--pct-pagination-item-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of an item."
        },
        {
          "name": "--pct-pagination-item-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Touch-target floor of an item."
        },
        {
          "name": "--pct-pagination-list-gap",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Gap between page buttons."
        },
        {
          "name": "--pct-pagination-marker-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the fold's ellipsis between page numbers."
        }
      ],
      "texts": [
        "paginationLabel",
        "paginationNext",
        "paginationPrevious"
      ],
      "keyboard": "| key | effect | test |\n| --- | ------ | ---- |\n|     |        |      |\n\nEmpty **by design**: every control in the strip is a native `<button>`, so `Tab` / `Shift+Tab`\nmove between them and `Enter` / `Space` activate — all the platform's\n([`req-api-platform`](../requirements/api.md#req-api-platform)). It is deliberately **not** a\n`toolbar` with a roving `tabindex`: the buttons are independent destinations, not a set\noperated as a unit, and a roving index would hide every page number but one from sequential\nkeyboard navigation and from a screen reader's \"next focusable\". The cost — one tab stop per\nrendered button — is capped by `siblingCount` / `boundaryCount` keeping the strip to about nine\nnumbers plus two steppers.",
      "decisions": [],
      "evidence": {
        "mutation": {
          "score": 96.43,
          "killed": 108,
          "survived": 4,
          "mutants": 112,
          "files": 1
        },
        "e2e": {
          "cases": 14,
          "spec": "apps/sandbox-e2e/src/pagination.spec.ts"
        },
        "pairs": 6,
        "baselines": 2,
        "cost": {
          "elements": 27,
          "depth": 7,
          "listeners": 8,
          "renders": 1,
          "micros": 7324,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "field",
      "classes": [
        "PctField"
      ],
      "role": "form control wrapper",
      "category": "Text & numbers",
      "status": "released",
      "entrypoint": "@pacit/components/field",
      "selectors": [
        "pct-field"
      ],
      "harnesses": [
        "PctFieldHarness",
        "PctPrefixHarness",
        "PctSuffixHarness",
        "PctLabelAuxHarness",
        "PctMessageAuxHarness"
      ],
      "docs": "/components/field",
      "summary": "The wrapper around a form control — its label, its hint, its error, and the wiring between the three.",
      "usage": {
        "code": "<pct-field label=\"E-mail\" hint=\"A work address\">\n  <input pctText type=\"email\" />\n</pct-field>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "affixes",
          "title": "Around the control",
          "code": "import { Component } from '@angular/core';\nimport {\n  PctField,\n  PctLabelAux,\n  PctMessageAux,\n  PctPrefix,\n  PctSuffix,\n  PctText,\n} from '@pacit/components/field';\n\n/**\n * Around the control\n *\n * `pctPrefix` and `pctSuffix` sit inside the row, beside the control; `pctLabelAux` and\n * `pctMessageAux` sit beside the label and the message — a counter, a link, a unit.\n */\n@Component({\n  selector: 'demo-field-affixes',\n  imports: [\n    PctField,\n    PctLabelAux,\n    PctMessageAux,\n    PctPrefix,\n    PctSuffix,\n    PctText,\n  ],\n  styles:\n    ':host { display: grid; gap: 16px; max-inline-size: 24rem; } a { color: var(--pct-primary); }',\n  template: `\n    <pct-field label=\"Price\" hint=\"Net, per seat\">\n      <span pctPrefix>€</span>\n      <input pctText inputmode=\"decimal\" value=\"12.00\" />\n      <span pctSuffix>/ month</span>\n    </pct-field>\n    <pct-field label=\"Handle\" hint=\"Letters and digits\">\n      <a pctLabelAux href=\"#rules\">Rules</a>\n      <span pctPrefix>&#64;</span>\n      <input pctText value=\"ada\" />\n      <span pctMessageAux>3 / 20</span>\n    </pct-field>\n  `,\n})\nexport class FieldAffixesDemo {}"
        }
      ],
      "api": [
        {
          "name": "PctField",
          "kind": "component",
          "selector": "pct-field",
          "description": "The chrome of a form field: label, hint, warning, error message, the required marker and the [pctPrefix] / [pctSuffix] slots inside the field.",
          "members": [
            {
              "name": "label",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The visible <label> of the row, wired to the control by for or by aria-labelledby — whichever the control's labelStrategy asks for.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "hint",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "A line of help under the row; an error or a warning takes its place while one shows (one message at a time, req-api-message).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "size",
              "kind": "input",
              "required": false,
              "type": "PctFieldSize",
              "default": "config.defaultSize",
              "description": "The field size; taken from the global configuration by default (req-api-config). It concerns the field row — the height here equals that of a button of the same size, because both take it from the --pct-control-height-* token (req-api-size).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "required",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Requiredness can be given outright when the control does not report it.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-field",
              "note": ""
            },
            {
              "attribute": "data-pct-size",
              "kind": "attribute",
              "bound": "size()",
              "note": ""
            },
            {
              "attribute": "data-pct-appearance",
              "kind": "attribute",
              "bound": "appearance()",
              "note": ""
            },
            {
              "attribute": "data-pct-cursor",
              "kind": "attribute",
              "bound": "cursor()",
              "note": ""
            },
            {
              "attribute": "data-pct-invalid",
              "kind": "attribute",
              "bound": "showInvalid() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-warning",
              "kind": "attribute",
              "bound": "showWarning() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-disabled",
              "kind": "attribute",
              "bound": "disabled() ? \"\" : null",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctField",
          "kind": "component",
          "selector": "pct-field",
          "detail": null,
          "description": "The chrome of a form field: label, hint, warning, error message, the required marker and the [pctPrefix] / [pctSuffix] slots inside the field.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctFieldSize",
          "kind": "type",
          "selector": null,
          "detail": "PctSize",
          "description": "The size of a field: the library's own scale.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctText",
          "kind": "component",
          "selector": "input[pctText], textarea[pctText]",
          "detail": null,
          "description": "Text field: a directive on a native <input>. The input is not wrapped in a component of ours, so type, browser autofill, mobile keyboard modes and the whole semantics are kept without an abstraction in between (req-api-platform).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctAutosize",
          "kind": "directive",
          "selector": "textarea[pctText][pctAutosize]",
          "detail": null,
          "description": "A <textarea> as tall as what is written in it — the ARIA APG has no pattern for this, because it is not a behaviour but a height.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctNumber",
          "kind": "component",
          "selector": "input[pctNumber]",
          "detail": null,
          "description": "Number field: a component on a native <input type=\"text\"> with the spinbutton role, a value of type number | null and locale-aware formatting.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctAffixFit",
          "kind": "type",
          "selector": null,
          "detail": "'inset' | 'fill'",
          "description": "How a decoration sits in its slot:",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctPrefix",
          "kind": "directive",
          "selector": "[pctPrefix]",
          "detail": null,
          "description": "A decoration before the control, inside the field border (a currency unit, an icon). Decorative content should be hidden from the screen reader (aria-hidden), or have an accessible name of its own if it is interactive.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctSuffix",
          "kind": "directive",
          "selector": "[pctSuffix]",
          "detail": null,
          "description": "A decoration after the control, inside the field border (a clear button, say).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctLabelAux",
          "kind": "directive",
          "selector": "[pctLabelAux]",
          "detail": null,
          "description": "A label add-on — content in the label row, aligned to the end (an \"i\" icon with a hint about the field, a help link). It lies outside the field border, so it does not interfere with the control's touch area. If it is interactive (a button, a link) it needs an accessible name of its own.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctMessageAux",
          "kind": "directive",
          "selector": "[pctMessageAux]",
          "detail": null,
          "description": "A message-line add-on — content in the row below the field, aligned to the end (a character counter, say). It shares the row with the hint or the error: the chrome shows only one of those messages below the field, and this slot stands beside it whichever one is lit.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "field-control",
          "description": "the wrapped control itself"
        },
        {
          "name": "field-error",
          "description": "the message when the control is invalid"
        },
        {
          "name": "field-footer",
          "description": "the row under the control: the hint, the warning or the error"
        },
        {
          "name": "field-header",
          "description": "the row above the control: the label and what stands beside it"
        },
        {
          "name": "field-hint",
          "description": "the hint, read as the description of the control"
        },
        {
          "name": "field-label",
          "description": "the label, tied to the control by id"
        },
        {
          "name": "field-label-aux",
          "description": "the slot beside the label — a counter, a link"
        },
        {
          "name": "field-message-aux",
          "description": "the slot beside the hint, the warning or the error"
        },
        {
          "name": "field-prefix",
          "description": "what sits before the control inside the row"
        },
        {
          "name": "field-row",
          "description": "the control's row, with its prefix and suffix"
        },
        {
          "name": "field-suffix",
          "description": "what sits after the control inside the row"
        },
        {
          "name": "field-warning",
          "description": "the message when the value is allowed and suspect — after the error, before the hint, in the warning tone with a glyph and a hidden word (0087)"
        },
        {
          "name": "field-label-aux-item",
          "description": "one projected item inside the label slot"
        },
        {
          "name": "field-message-aux-item",
          "description": "one projected item inside the message slot"
        },
        {
          "name": "field-prefix-item",
          "description": "one projected item inside the prefix"
        },
        {
          "name": "field-suffix-item",
          "description": "one projected item inside the suffix"
        }
      ],
      "tokens": [
        {
          "name": "--pct-field-affix-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of an affix."
        },
        {
          "name": "--pct-field-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the field."
        },
        {
          "name": "--pct-field-bg-disabled",
          "type": "color",
          "ref": "{pct.surface-disabled}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the field while disabled."
        },
        {
          "name": "--pct-field-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the field."
        },
        {
          "name": "--pct-field-border-disabled",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge colour of the field while disabled."
        },
        {
          "name": "--pct-field-border-focus",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge colour of the field when focused."
        },
        {
          "name": "--pct-field-border-hover",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Edge colour of the field on hover."
        },
        {
          "name": "--pct-field-border-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Edge colour of the field when invalid."
        },
        {
          "name": "--pct-field-border-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Edge colour of the field under a warning (0087)."
        },
        {
          "name": "--pct-field-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the field."
        },
        {
          "name": "--pct-field-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of the field while disabled."
        },
        {
          "name": "--pct-field-fg-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Text colour of the field when invalid."
        },
        {
          "name": "--pct-field-fg-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Text colour of the warning line (0087)."
        },
        {
          "name": "--pct-field-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of the field."
        },
        {
          "name": "--pct-field-font-size-lg",
          "type": "dimension",
          "ref": "{pct.font.size.lg}",
          "light": "16px",
          "dark": "16px",
          "description": "Font size of the field at size lg."
        },
        {
          "name": "--pct-field-font-size-sm",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of the field at size sm."
        },
        {
          "name": "--pct-field-gap",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Gap between the field's label, control and hint."
        },
        {
          "name": "--pct-field-height",
          "type": "dimension",
          "ref": "{pct.control.height.md}",
          "light": "36px",
          "dark": "36px",
          "description": "Height of the field."
        },
        {
          "name": "--pct-field-height-lg",
          "type": "dimension",
          "ref": "{pct.control.height.lg}",
          "light": "44px",
          "dark": "44px",
          "description": "Height of the field at size lg."
        },
        {
          "name": "--pct-field-height-sm",
          "type": "dimension",
          "ref": "{pct.control.height.sm}",
          "light": "28px",
          "dark": "28px",
          "description": "Height of the field at size sm."
        },
        {
          "name": "--pct-field-hint-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the hint."
        },
        {
          "name": "--pct-field-label-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the label."
        },
        {
          "name": "--pct-field-label-gap",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Gap inside the label."
        },
        {
          "name": "--pct-field-padding-x",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of the field."
        },
        {
          "name": "--pct-field-padding-x-lg",
          "type": "dimension",
          "ref": "{pct.space.5}",
          "light": "16px",
          "dark": "16px",
          "description": "Horizontal padding of the field at size lg."
        },
        {
          "name": "--pct-field-padding-x-sm",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of the field at size sm."
        },
        {
          "name": "--pct-field-placeholder-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the placeholder."
        },
        {
          "name": "--pct-field-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the field."
        }
      ],
      "texts": [
        "fieldWarning",
        "numberMalformed"
      ],
      "keyboard": "None of its own — the wrapper is not focusable. A click on the row's background is\n**forwarded to the control**: `focus()` on `mousedown`, `activate()` on `click`.",
      "decisions": [
        {
          "id": "0003",
          "title": "Wrapper and control"
        },
        {
          "id": "0004",
          "title": "Height stated outright, not derived from padding"
        },
        {
          "id": "0006",
          "title": "The anchor and inheritance in an overlay"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 75.83,
          "killed": 91,
          "survived": 26,
          "mutants": 120,
          "files": 1
        },
        "e2e": {
          "cases": 10,
          "spec": "apps/sandbox-e2e/src/field.spec.ts"
        },
        "pairs": 14,
        "baselines": 5,
        "cost": {
          "elements": 14,
          "depth": 4,
          "listeners": 4,
          "renders": 1,
          "micros": 7844,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "text",
      "classes": [
        "PctText"
      ],
      "role": "text field",
      "category": "Text & numbers",
      "status": "released",
      "entrypoint": "@pacit/components/field",
      "selectors": [
        "input[pctText]"
      ],
      "harnesses": [
        "PctTextHarness"
      ],
      "docs": "/components/text",
      "summary": "A text field on the platform's own <input> element, wired to the label and the error around it.",
      "usage": {
        "code": "<pct-field label=\"Workspace name\">\n  <input pctText />\n</pct-field>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "states",
          "title": "Read-only and disabled are two states",
          "code": "import { Component } from '@angular/core';\nimport { PctField, PctText } from '@pacit/components/field';\n\n/**\n * Read-only and disabled are two states\n *\n * Read-only keeps the value in the tab order and lets it be copied; disabled takes it out.\n * Both are the input's native attributes, so the platform announces them itself.\n */\n@Component({\n  selector: 'demo-text-states',\n  imports: [PctField, PctText],\n  styles: ':host { display: grid; gap: 16px; max-inline-size: 24rem; }',\n  template: `\n    <pct-field label=\"Account id\" hint=\"Copy it; it cannot change\">\n      <input pctText value=\"acct_8f3k2\" readonly />\n    </pct-field>\n    <pct-field label=\"Legacy key\" hint=\"Retired with version 2\">\n      <input pctText value=\"••••••••\" disabled />\n    </pct-field>\n  `,\n})\nexport class TextStatesDemo {}"
        }
      ],
      "api": [
        {
          "name": "PctText",
          "kind": "component",
          "selector": "input[pctText], textarea[pctText]",
          "description": "Text field: a directive on a native <input>. The input is not wrapped in a component of ours, so type, browser autofill, mobile keyboard modes and the whole semantics are kept without an abstraction in between (req-api-platform).",
          "members": [
            {
              "name": "value",
              "kind": "model",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The value — a required field of the FormValueControl contract.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "disabled",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Blocks the control and greys it — the native disabled, so it leaves the tab order as well. With [formField] the directive writes it, as it writes every input of the FormUiControl contract.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "readonly",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Keeps the value from being edited while the field stays focusable and readable — the native readonly.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "invalid",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "The form's verdict; shown only once touched, so an empty form does not open red.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "touched",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the user has left the field once; with invalid it gates the error face.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "required",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "The native required; a pct-field around the control reads it to mark the label.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "errors",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[]",
              "default": "[]",
              "description": "The form's validation errors; a pct-field around the control shows the first one's message in place of the hint once the field is touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "warnings",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[] | undefined",
              "default": "undefined",
              "description": "A verdict without a veto (0087): shown after the error and before the hint once the field is touched, in the warning tone, with aria-invalid untouched. Left unbound, the control reads what pctWarn() wrote for its own [formField]; bound — [] included — the list given stands.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "name",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The native name — what a form submission calls the value.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "touch",
              "kind": "output",
              "required": false,
              "type": "void",
              "default": null,
              "description": "Emitted on blur — lets the form mark the field as touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-text",
              "note": ""
            },
            {
              "attribute": "id",
              "kind": "property",
              "bound": "controlId",
              "note": ""
            },
            {
              "attribute": "disabled",
              "kind": "property",
              "bound": "disabled()",
              "note": ""
            },
            {
              "attribute": "readOnly",
              "kind": "property",
              "bound": "readonly()",
              "note": ""
            },
            {
              "attribute": "name",
              "kind": "attribute",
              "bound": "name() || null",
              "note": ""
            },
            {
              "attribute": "required",
              "kind": "attribute",
              "bound": "required() || null",
              "note": ""
            },
            {
              "attribute": "aria-invalid",
              "kind": "attribute",
              "bound": "showInvalid() ? \"true\" : null",
              "note": ""
            },
            {
              "attribute": "aria-describedby",
              "kind": "attribute",
              "bound": "describedBy()",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctField",
          "kind": "component",
          "selector": "pct-field",
          "detail": null,
          "description": "The chrome of a form field: label, hint, warning, error message, the required marker and the [pctPrefix] / [pctSuffix] slots inside the field.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctFieldSize",
          "kind": "type",
          "selector": null,
          "detail": "PctSize",
          "description": "The size of a field: the library's own scale.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctText",
          "kind": "component",
          "selector": "input[pctText], textarea[pctText]",
          "detail": null,
          "description": "Text field: a directive on a native <input>. The input is not wrapped in a component of ours, so type, browser autofill, mobile keyboard modes and the whole semantics are kept without an abstraction in between (req-api-platform).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctAutosize",
          "kind": "directive",
          "selector": "textarea[pctText][pctAutosize]",
          "detail": null,
          "description": "A <textarea> as tall as what is written in it — the ARIA APG has no pattern for this, because it is not a behaviour but a height.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctNumber",
          "kind": "component",
          "selector": "input[pctNumber]",
          "detail": null,
          "description": "Number field: a component on a native <input type=\"text\"> with the spinbutton role, a value of type number | null and locale-aware formatting.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctAffixFit",
          "kind": "type",
          "selector": null,
          "detail": "'inset' | 'fill'",
          "description": "How a decoration sits in its slot:",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctPrefix",
          "kind": "directive",
          "selector": "[pctPrefix]",
          "detail": null,
          "description": "A decoration before the control, inside the field border (a currency unit, an icon). Decorative content should be hidden from the screen reader (aria-hidden), or have an accessible name of its own if it is interactive.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctSuffix",
          "kind": "directive",
          "selector": "[pctSuffix]",
          "detail": null,
          "description": "A decoration after the control, inside the field border (a clear button, say).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctLabelAux",
          "kind": "directive",
          "selector": "[pctLabelAux]",
          "detail": null,
          "description": "A label add-on — content in the label row, aligned to the end (an \"i\" icon with a hint about the field, a help link). It lies outside the field border, so it does not interfere with the control's touch area. If it is interactive (a button, a link) it needs an accessible name of its own.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctMessageAux",
          "kind": "directive",
          "selector": "[pctMessageAux]",
          "detail": null,
          "description": "A message-line add-on — content in the row below the field, aligned to the end (a character counter, say). It shares the row with the hint or the error: the chrome shows only one of those messages below the field, and this slot stands beside it whichever one is lit.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "field-control",
          "description": "the wrapped control itself"
        },
        {
          "name": "field-error",
          "description": "the message when the control is invalid"
        },
        {
          "name": "field-footer",
          "description": "the row under the control: the hint, the warning or the error"
        },
        {
          "name": "field-header",
          "description": "the row above the control: the label and what stands beside it"
        },
        {
          "name": "field-hint",
          "description": "the hint, read as the description of the control"
        },
        {
          "name": "field-label",
          "description": "the label, tied to the control by id"
        },
        {
          "name": "field-label-aux",
          "description": "the slot beside the label — a counter, a link"
        },
        {
          "name": "field-message-aux",
          "description": "the slot beside the hint, the warning or the error"
        },
        {
          "name": "field-prefix",
          "description": "what sits before the control inside the row"
        },
        {
          "name": "field-row",
          "description": "the control's row, with its prefix and suffix"
        },
        {
          "name": "field-suffix",
          "description": "what sits after the control inside the row"
        },
        {
          "name": "field-warning",
          "description": "the message when the value is allowed and suspect — after the error, before the hint, in the warning tone with a glyph and a hidden word (0087)"
        },
        {
          "name": "field-label-aux-item",
          "description": "one projected item inside the label slot"
        },
        {
          "name": "field-message-aux-item",
          "description": "one projected item inside the message slot"
        },
        {
          "name": "field-prefix-item",
          "description": "one projected item inside the prefix"
        },
        {
          "name": "field-suffix-item",
          "description": "one projected item inside the suffix"
        }
      ],
      "tokens": [
        {
          "name": "--pct-field-affix-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of an affix."
        },
        {
          "name": "--pct-field-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the field."
        },
        {
          "name": "--pct-field-bg-disabled",
          "type": "color",
          "ref": "{pct.surface-disabled}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the field while disabled."
        },
        {
          "name": "--pct-field-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the field."
        },
        {
          "name": "--pct-field-border-disabled",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge colour of the field while disabled."
        },
        {
          "name": "--pct-field-border-focus",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge colour of the field when focused."
        },
        {
          "name": "--pct-field-border-hover",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Edge colour of the field on hover."
        },
        {
          "name": "--pct-field-border-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Edge colour of the field when invalid."
        },
        {
          "name": "--pct-field-border-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Edge colour of the field under a warning (0087)."
        },
        {
          "name": "--pct-field-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the field."
        },
        {
          "name": "--pct-field-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of the field while disabled."
        },
        {
          "name": "--pct-field-fg-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Text colour of the field when invalid."
        },
        {
          "name": "--pct-field-fg-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Text colour of the warning line (0087)."
        },
        {
          "name": "--pct-field-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of the field."
        },
        {
          "name": "--pct-field-font-size-lg",
          "type": "dimension",
          "ref": "{pct.font.size.lg}",
          "light": "16px",
          "dark": "16px",
          "description": "Font size of the field at size lg."
        },
        {
          "name": "--pct-field-font-size-sm",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of the field at size sm."
        },
        {
          "name": "--pct-field-gap",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Gap between the field's label, control and hint."
        },
        {
          "name": "--pct-field-height",
          "type": "dimension",
          "ref": "{pct.control.height.md}",
          "light": "36px",
          "dark": "36px",
          "description": "Height of the field."
        },
        {
          "name": "--pct-field-height-lg",
          "type": "dimension",
          "ref": "{pct.control.height.lg}",
          "light": "44px",
          "dark": "44px",
          "description": "Height of the field at size lg."
        },
        {
          "name": "--pct-field-height-sm",
          "type": "dimension",
          "ref": "{pct.control.height.sm}",
          "light": "28px",
          "dark": "28px",
          "description": "Height of the field at size sm."
        },
        {
          "name": "--pct-field-hint-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the hint."
        },
        {
          "name": "--pct-field-label-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the label."
        },
        {
          "name": "--pct-field-label-gap",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Gap inside the label."
        },
        {
          "name": "--pct-field-padding-x",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of the field."
        },
        {
          "name": "--pct-field-padding-x-lg",
          "type": "dimension",
          "ref": "{pct.space.5}",
          "light": "16px",
          "dark": "16px",
          "description": "Horizontal padding of the field at size lg."
        },
        {
          "name": "--pct-field-padding-x-sm",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of the field at size sm."
        },
        {
          "name": "--pct-field-placeholder-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the placeholder."
        },
        {
          "name": "--pct-field-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the field."
        }
      ],
      "texts": [
        "fieldWarning",
        "numberMalformed"
      ],
      "keyboard": "None of its own — fully native\n([`req-api-platform`](../requirements/api.md#req-api-platform)).",
      "decisions": [
        {
          "id": "0003",
          "title": "Wrapper and control"
        },
        {
          "id": "0005",
          "title": "Signal forms without ControlValueAccessor"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 85.37,
          "killed": 35,
          "survived": 6,
          "mutants": 41,
          "files": 1
        },
        "e2e": {
          "cases": 10,
          "spec": "apps/sandbox-e2e/src/field.spec.ts"
        },
        "pairs": 0,
        "baselines": 2,
        "cost": {
          "elements": 13,
          "depth": 4,
          "listeners": 4,
          "renders": 1,
          "micros": 5155,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "textarea",
      "classes": [
        "PctAutosize"
      ],
      "role": "a textarea as tall as its text",
      "category": "Text & numbers",
      "status": "released",
      "entrypoint": "@pacit/components/field",
      "selectors": [
        "textarea[pctText][pctAutosize]"
      ],
      "harnesses": [
        "PctTextHarness",
        "PctAutosizeHarness"
      ],
      "docs": "/components/textarea",
      "summary": "A textarea as tall as its text, growing and shrinking as the reader types.",
      "usage": {
        "code": "<pct-field label=\"Release notes\">\n  <textarea pctText pctAutosize rows=\"2\"></textarea>\n</pct-field>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "rows",
          "title": "It grows, up to a point",
          "code": "import { Component } from '@angular/core';\nimport { PctAutosize, PctField, PctText } from '@pacit/components/field';\n\n/**\n * It grows, up to a point\n *\n * `rows` is the height it starts at; `maxRows` is where it stops growing and starts\n * scrolling instead — a comment box, not a document.\n */\n@Component({\n  selector: 'demo-textarea-rows',\n  imports: [PctAutosize, PctField, PctText],\n  styles: ':host { display: block; max-inline-size: 28rem; }',\n  template: `\n    <pct-field label=\"Comment\" hint=\"Grows to five lines, then scrolls\">\n      <textarea pctText pctAutosize rows=\"2\" [maxRows]=\"5\"></textarea>\n    </pct-field>\n  `,\n})\nexport class TextareaRowsDemo {}"
        }
      ],
      "api": [
        {
          "name": "PctAutosize",
          "kind": "directive",
          "selector": "textarea[pctText][pctAutosize]",
          "description": "A <textarea> as tall as what is written in it — the ARIA APG has no pattern for this, because it is not a behaviour but a height.",
          "members": [
            {
              "name": "rows",
              "kind": "input",
              "required": false,
              "type": "number",
              "default": "2",
              "description": "The floor, in lines — the platform's own rows, kept as the platform spells it (req-api-platform) and read here only because field-sizing: content discards it: measured, an empty rows=\"2\" box under the property is 34 px, one line, against the 58 a plain one is.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "maxRows",
              "kind": "input",
              "required": false,
              "type": "number",
              "default": "0",
              "description": "The ceiling, in lines; 0 is no ceiling. This one has no native spelling, so it is an input. Past it the box scrolls rather than clipping — measured in three engines, on both roads.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "data-pct-autosize",
              "kind": "static",
              "bound": "",
              "note": ""
            },
            {
              "attribute": "rows",
              "kind": "attribute",
              "bound": "rows()",
              "note": "Written back to the DOM because the input above has taken it: a bound <code>[rows]</code> is consumed by a directive input and never reaches the element, and the measured road takes its floor from the element's own <code>rows</code> sizing. Both roads read one number."
            },
            {
              "attribute": "[style.--_pct-text-rows]",
              "kind": "property",
              "bound": "rows()",
              "note": ""
            },
            {
              "attribute": "data-pct-capped",
              "kind": "attribute",
              "bound": "maxRows() > 0 ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "[style.max-block-size]",
              "kind": "property",
              "bound": "maxRows() > 0 ? maxRows() + \"lh\" : null",
              "note": "<code>lh</code> is the element's own line box — the arithmetic stays in the engine, in both roads."
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctField",
          "kind": "component",
          "selector": "pct-field",
          "detail": null,
          "description": "The chrome of a form field: label, hint, warning, error message, the required marker and the [pctPrefix] / [pctSuffix] slots inside the field.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctFieldSize",
          "kind": "type",
          "selector": null,
          "detail": "PctSize",
          "description": "The size of a field: the library's own scale.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctText",
          "kind": "component",
          "selector": "input[pctText], textarea[pctText]",
          "detail": null,
          "description": "Text field: a directive on a native <input>. The input is not wrapped in a component of ours, so type, browser autofill, mobile keyboard modes and the whole semantics are kept without an abstraction in between (req-api-platform).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctAutosize",
          "kind": "directive",
          "selector": "textarea[pctText][pctAutosize]",
          "detail": null,
          "description": "A <textarea> as tall as what is written in it — the ARIA APG has no pattern for this, because it is not a behaviour but a height.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctNumber",
          "kind": "component",
          "selector": "input[pctNumber]",
          "detail": null,
          "description": "Number field: a component on a native <input type=\"text\"> with the spinbutton role, a value of type number | null and locale-aware formatting.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctAffixFit",
          "kind": "type",
          "selector": null,
          "detail": "'inset' | 'fill'",
          "description": "How a decoration sits in its slot:",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctPrefix",
          "kind": "directive",
          "selector": "[pctPrefix]",
          "detail": null,
          "description": "A decoration before the control, inside the field border (a currency unit, an icon). Decorative content should be hidden from the screen reader (aria-hidden), or have an accessible name of its own if it is interactive.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctSuffix",
          "kind": "directive",
          "selector": "[pctSuffix]",
          "detail": null,
          "description": "A decoration after the control, inside the field border (a clear button, say).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctLabelAux",
          "kind": "directive",
          "selector": "[pctLabelAux]",
          "detail": null,
          "description": "A label add-on — content in the label row, aligned to the end (an \"i\" icon with a hint about the field, a help link). It lies outside the field border, so it does not interfere with the control's touch area. If it is interactive (a button, a link) it needs an accessible name of its own.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctMessageAux",
          "kind": "directive",
          "selector": "[pctMessageAux]",
          "detail": null,
          "description": "A message-line add-on — content in the row below the field, aligned to the end (a character counter, say). It shares the row with the hint or the error: the chrome shows only one of those messages below the field, and this slot stands beside it whichever one is lit.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "field-control",
          "description": "the wrapped control itself"
        },
        {
          "name": "field-error",
          "description": "the message when the control is invalid"
        },
        {
          "name": "field-footer",
          "description": "the row under the control: the hint, the warning or the error"
        },
        {
          "name": "field-header",
          "description": "the row above the control: the label and what stands beside it"
        },
        {
          "name": "field-hint",
          "description": "the hint, read as the description of the control"
        },
        {
          "name": "field-label",
          "description": "the label, tied to the control by id"
        },
        {
          "name": "field-label-aux",
          "description": "the slot beside the label — a counter, a link"
        },
        {
          "name": "field-message-aux",
          "description": "the slot beside the hint, the warning or the error"
        },
        {
          "name": "field-prefix",
          "description": "what sits before the control inside the row"
        },
        {
          "name": "field-row",
          "description": "the control's row, with its prefix and suffix"
        },
        {
          "name": "field-suffix",
          "description": "what sits after the control inside the row"
        },
        {
          "name": "field-warning",
          "description": "the message when the value is allowed and suspect — after the error, before the hint, in the warning tone with a glyph and a hidden word (0087)"
        },
        {
          "name": "field-label-aux-item",
          "description": "one projected item inside the label slot"
        },
        {
          "name": "field-message-aux-item",
          "description": "one projected item inside the message slot"
        },
        {
          "name": "field-prefix-item",
          "description": "one projected item inside the prefix"
        },
        {
          "name": "field-suffix-item",
          "description": "one projected item inside the suffix"
        }
      ],
      "tokens": [
        {
          "name": "--pct-field-affix-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of an affix."
        },
        {
          "name": "--pct-field-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the field."
        },
        {
          "name": "--pct-field-bg-disabled",
          "type": "color",
          "ref": "{pct.surface-disabled}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the field while disabled."
        },
        {
          "name": "--pct-field-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the field."
        },
        {
          "name": "--pct-field-border-disabled",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge colour of the field while disabled."
        },
        {
          "name": "--pct-field-border-focus",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge colour of the field when focused."
        },
        {
          "name": "--pct-field-border-hover",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Edge colour of the field on hover."
        },
        {
          "name": "--pct-field-border-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Edge colour of the field when invalid."
        },
        {
          "name": "--pct-field-border-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Edge colour of the field under a warning (0087)."
        },
        {
          "name": "--pct-field-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the field."
        },
        {
          "name": "--pct-field-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of the field while disabled."
        },
        {
          "name": "--pct-field-fg-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Text colour of the field when invalid."
        },
        {
          "name": "--pct-field-fg-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Text colour of the warning line (0087)."
        },
        {
          "name": "--pct-field-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of the field."
        },
        {
          "name": "--pct-field-font-size-lg",
          "type": "dimension",
          "ref": "{pct.font.size.lg}",
          "light": "16px",
          "dark": "16px",
          "description": "Font size of the field at size lg."
        },
        {
          "name": "--pct-field-font-size-sm",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of the field at size sm."
        },
        {
          "name": "--pct-field-gap",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Gap between the field's label, control and hint."
        },
        {
          "name": "--pct-field-height",
          "type": "dimension",
          "ref": "{pct.control.height.md}",
          "light": "36px",
          "dark": "36px",
          "description": "Height of the field."
        },
        {
          "name": "--pct-field-height-lg",
          "type": "dimension",
          "ref": "{pct.control.height.lg}",
          "light": "44px",
          "dark": "44px",
          "description": "Height of the field at size lg."
        },
        {
          "name": "--pct-field-height-sm",
          "type": "dimension",
          "ref": "{pct.control.height.sm}",
          "light": "28px",
          "dark": "28px",
          "description": "Height of the field at size sm."
        },
        {
          "name": "--pct-field-hint-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the hint."
        },
        {
          "name": "--pct-field-label-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the label."
        },
        {
          "name": "--pct-field-label-gap",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Gap inside the label."
        },
        {
          "name": "--pct-field-padding-x",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of the field."
        },
        {
          "name": "--pct-field-padding-x-lg",
          "type": "dimension",
          "ref": "{pct.space.5}",
          "light": "16px",
          "dark": "16px",
          "description": "Horizontal padding of the field at size lg."
        },
        {
          "name": "--pct-field-padding-x-sm",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of the field at size sm."
        },
        {
          "name": "--pct-field-placeholder-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the placeholder."
        },
        {
          "name": "--pct-field-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the field."
        }
      ],
      "texts": [
        "fieldWarning",
        "numberMalformed"
      ],
      "keyboard": "None of its own — fully native\n([`req-api-platform`](../requirements/api.md#req-api-platform)).",
      "decisions": [
        {
          "id": "0041",
          "title": "A height the platform computes, and a measurement only where it does not"
        },
        {
          "id": "0034",
          "title": "Multiplicity is a type, so it is a tag"
        },
        {
          "id": "0003",
          "title": "Wrapper and control"
        },
        {
          "id": "0004",
          "title": "Height stated outright, not derived from padding"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 77.03,
          "killed": 57,
          "survived": 16,
          "mutants": 74,
          "files": 1
        },
        "e2e": {
          "cases": 7,
          "spec": "apps/sandbox-e2e/src/textarea.spec.ts"
        },
        "pairs": 0,
        "baselines": 0,
        "cost": {
          "elements": 13,
          "depth": 4,
          "listeners": 4,
          "renders": 1,
          "micros": 14256,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "number",
      "classes": [
        "PctNumber"
      ],
      "role": "number field",
      "category": "Text & numbers",
      "status": "released",
      "entrypoint": "@pacit/components/field",
      "selectors": [
        "input[pctNumber]"
      ],
      "harnesses": [
        "PctNumberHarness"
      ],
      "docs": "/components/number",
      "summary": "A number field with a step and bounds, and the keyboard the platform already gives it.",
      "usage": {
        "code": "<pct-field label=\"Monthly budget\">\n  <input pctNumber [formField]=\"limits.budget\" />\n</pct-field>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "format",
          "title": "Digits the way you write them",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctField, PctNumber } from '@pacit/components/field';\n\n/**\n * Digits the way you write them\n *\n * The value is a number; what you type is parsed in your own locale and written back the\n * same way. `step` sets what the arrows add, `minFractionDigits` how many decimals always show.\n */\n@Component({\n  selector: 'demo-number-format',\n  imports: [PctField, PctNumber],\n  styles: ':host { display: grid; gap: 12px; max-inline-size: 24rem; }',\n  template: `\n    <pct-field label=\"Unit price\" hint=\"Arrows move by 0.25\">\n      <input\n        pctNumber\n        [(value)]=\"price\"\n        [step]=\"0.25\"\n        [minFractionDigits]=\"2\"\n      />\n    </pct-field>\n    <p>Stored as {{ price() }}.</p>\n  `,\n})\nexport class NumberFormatDemo {\n  readonly price = signal<number | null>(19.5);\n}"
        }
      ],
      "api": [
        {
          "name": "PctNumber",
          "kind": "component",
          "selector": "input[pctNumber]",
          "description": "Number field: a component on a native <input type=\"text\"> with the spinbutton role, a value of type number | null and locale-aware formatting.",
          "members": [
            {
              "name": "value",
              "kind": "model",
              "required": false,
              "type": "number | null",
              "default": "null",
              "description": "The value — null means the field is empty.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "disabled",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Blocks the control and greys it — the native disabled, so it leaves the tab order as well. With [formField] the directive writes it, as it writes every input of the FormUiControl contract.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "readonly",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Keeps the value from being edited while the field stays focusable and readable — the native readonly.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "invalid",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "The form's verdict; shown only once touched, so an empty form does not open red.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "touched",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the user has left the field once; with invalid it gates the error face.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "required",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "The native required; a pct-field around the control reads it to mark the label.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "errors",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[]",
              "default": "[]",
              "description": "The form's validation errors; a pct-field around the control shows the first one's message in place of the hint once the field is touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "warnings",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[] | undefined",
              "default": "undefined",
              "description": "A verdict without a veto (0087): shown after the error and before the hint once the field is touched, in the warning tone, with aria-invalid untouched. Left unbound, the control reads what pctWarn() wrote for its own [formField]; bound — [] included — the list given stands.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "name",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The native name — what a form submission calls the value.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "autocomplete",
              "kind": "input",
              "required": false,
              "type": "AutoFill",
              "default": "'off'",
              "description": "The field's purpose for a browser's autofill, off by default — the value the control wrote by itself until it took this input.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "touch",
              "kind": "output",
              "required": false,
              "type": "void",
              "default": null,
              "description": "Emitted on blur — lets the form mark the field as touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "min",
              "kind": "input",
              "required": false,
              "type": "unknown",
              "default": "undefined",
              "description": "Value bounds. They belong to the FormUiControl contract, so with [formField] the directive fills them itself from the schema's min() / max() validators — there is no need to repeat them in the template. The value is clamped to them on commit.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "max",
              "kind": "input",
              "required": false,
              "type": "unknown",
              "default": "undefined",
              "description": "The upper bound — as min: filled by the directive from the schema's max(), and the value is clamped to it on commit.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "step",
              "kind": "input",
              "required": false,
              "type": "number",
              "default": "1",
              "description": "The up/down arrow step; PageUp/PageDown jumps ten times as far.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "minFractionDigits",
              "kind": "input",
              "required": false,
              "type": "number",
              "default": "0",
              "description": "Minimum number of decimal places written (2 for amounts, say: \"12.50\").",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "maxFractionDigits",
              "kind": "input",
              "required": false,
              "type": "number",
              "default": "0",
              "description": "Maximum number of decimal places; 0 (the default) = an integer.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "useGrouping",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "true",
              "description": "Locale-aware thousands grouping (\"1 234 567\").",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "locale",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "Overrides the application's LOCALE_ID for this field.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-text pct-number",
              "note": ""
            },
            {
              "attribute": "type",
              "kind": "static",
              "bound": "text",
              "note": ""
            },
            {
              "attribute": "role",
              "kind": "static",
              "bound": "spinbutton",
              "note": ""
            },
            {
              "attribute": "autocomplete",
              "kind": "attribute",
              "bound": "autocomplete()",
              "note": ""
            },
            {
              "attribute": "id",
              "kind": "property",
              "bound": "controlId",
              "note": ""
            },
            {
              "attribute": "inputmode",
              "kind": "attribute",
              "bound": "inputMode()",
              "note": ""
            },
            {
              "attribute": "disabled",
              "kind": "property",
              "bound": "disabled()",
              "note": ""
            },
            {
              "attribute": "readOnly",
              "kind": "property",
              "bound": "readonly()",
              "note": ""
            },
            {
              "attribute": "name",
              "kind": "attribute",
              "bound": "name() || null",
              "note": ""
            },
            {
              "attribute": "required",
              "kind": "attribute",
              "bound": "required() || null",
              "note": ""
            },
            {
              "attribute": "aria-invalid",
              "kind": "attribute",
              "bound": "showInvalid() ? \"true\" : null",
              "note": ""
            },
            {
              "attribute": "aria-describedby",
              "kind": "attribute",
              "bound": "describedBy()",
              "note": ""
            },
            {
              "attribute": "aria-valuenow",
              "kind": "attribute",
              "bound": "value()",
              "note": ""
            },
            {
              "attribute": "aria-valuetext",
              "kind": "attribute",
              "bound": "valueText()",
              "note": ""
            },
            {
              "attribute": "aria-valuemin",
              "kind": "attribute",
              "bound": "min()",
              "note": ""
            },
            {
              "attribute": "aria-valuemax",
              "kind": "attribute",
              "bound": "max()",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctField",
          "kind": "component",
          "selector": "pct-field",
          "detail": null,
          "description": "The chrome of a form field: label, hint, warning, error message, the required marker and the [pctPrefix] / [pctSuffix] slots inside the field.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctFieldSize",
          "kind": "type",
          "selector": null,
          "detail": "PctSize",
          "description": "The size of a field: the library's own scale.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctText",
          "kind": "component",
          "selector": "input[pctText], textarea[pctText]",
          "detail": null,
          "description": "Text field: a directive on a native <input>. The input is not wrapped in a component of ours, so type, browser autofill, mobile keyboard modes and the whole semantics are kept without an abstraction in between (req-api-platform).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctAutosize",
          "kind": "directive",
          "selector": "textarea[pctText][pctAutosize]",
          "detail": null,
          "description": "A <textarea> as tall as what is written in it — the ARIA APG has no pattern for this, because it is not a behaviour but a height.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctNumber",
          "kind": "component",
          "selector": "input[pctNumber]",
          "detail": null,
          "description": "Number field: a component on a native <input type=\"text\"> with the spinbutton role, a value of type number | null and locale-aware formatting.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctAffixFit",
          "kind": "type",
          "selector": null,
          "detail": "'inset' | 'fill'",
          "description": "How a decoration sits in its slot:",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctPrefix",
          "kind": "directive",
          "selector": "[pctPrefix]",
          "detail": null,
          "description": "A decoration before the control, inside the field border (a currency unit, an icon). Decorative content should be hidden from the screen reader (aria-hidden), or have an accessible name of its own if it is interactive.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctSuffix",
          "kind": "directive",
          "selector": "[pctSuffix]",
          "detail": null,
          "description": "A decoration after the control, inside the field border (a clear button, say).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctLabelAux",
          "kind": "directive",
          "selector": "[pctLabelAux]",
          "detail": null,
          "description": "A label add-on — content in the label row, aligned to the end (an \"i\" icon with a hint about the field, a help link). It lies outside the field border, so it does not interfere with the control's touch area. If it is interactive (a button, a link) it needs an accessible name of its own.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctMessageAux",
          "kind": "directive",
          "selector": "[pctMessageAux]",
          "detail": null,
          "description": "A message-line add-on — content in the row below the field, aligned to the end (a character counter, say). It shares the row with the hint or the error: the chrome shows only one of those messages below the field, and this slot stands beside it whichever one is lit.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "field-control",
          "description": "the wrapped control itself"
        },
        {
          "name": "field-error",
          "description": "the message when the control is invalid"
        },
        {
          "name": "field-footer",
          "description": "the row under the control: the hint, the warning or the error"
        },
        {
          "name": "field-header",
          "description": "the row above the control: the label and what stands beside it"
        },
        {
          "name": "field-hint",
          "description": "the hint, read as the description of the control"
        },
        {
          "name": "field-label",
          "description": "the label, tied to the control by id"
        },
        {
          "name": "field-label-aux",
          "description": "the slot beside the label — a counter, a link"
        },
        {
          "name": "field-message-aux",
          "description": "the slot beside the hint, the warning or the error"
        },
        {
          "name": "field-prefix",
          "description": "what sits before the control inside the row"
        },
        {
          "name": "field-row",
          "description": "the control's row, with its prefix and suffix"
        },
        {
          "name": "field-suffix",
          "description": "what sits after the control inside the row"
        },
        {
          "name": "field-warning",
          "description": "the message when the value is allowed and suspect — after the error, before the hint, in the warning tone with a glyph and a hidden word (0087)"
        },
        {
          "name": "field-label-aux-item",
          "description": "one projected item inside the label slot"
        },
        {
          "name": "field-message-aux-item",
          "description": "one projected item inside the message slot"
        },
        {
          "name": "field-prefix-item",
          "description": "one projected item inside the prefix"
        },
        {
          "name": "field-suffix-item",
          "description": "one projected item inside the suffix"
        }
      ],
      "tokens": [
        {
          "name": "--pct-field-affix-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of an affix."
        },
        {
          "name": "--pct-field-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the field."
        },
        {
          "name": "--pct-field-bg-disabled",
          "type": "color",
          "ref": "{pct.surface-disabled}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the field while disabled."
        },
        {
          "name": "--pct-field-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the field."
        },
        {
          "name": "--pct-field-border-disabled",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge colour of the field while disabled."
        },
        {
          "name": "--pct-field-border-focus",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge colour of the field when focused."
        },
        {
          "name": "--pct-field-border-hover",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Edge colour of the field on hover."
        },
        {
          "name": "--pct-field-border-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Edge colour of the field when invalid."
        },
        {
          "name": "--pct-field-border-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Edge colour of the field under a warning (0087)."
        },
        {
          "name": "--pct-field-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the field."
        },
        {
          "name": "--pct-field-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of the field while disabled."
        },
        {
          "name": "--pct-field-fg-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Text colour of the field when invalid."
        },
        {
          "name": "--pct-field-fg-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Text colour of the warning line (0087)."
        },
        {
          "name": "--pct-field-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of the field."
        },
        {
          "name": "--pct-field-font-size-lg",
          "type": "dimension",
          "ref": "{pct.font.size.lg}",
          "light": "16px",
          "dark": "16px",
          "description": "Font size of the field at size lg."
        },
        {
          "name": "--pct-field-font-size-sm",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of the field at size sm."
        },
        {
          "name": "--pct-field-gap",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Gap between the field's label, control and hint."
        },
        {
          "name": "--pct-field-height",
          "type": "dimension",
          "ref": "{pct.control.height.md}",
          "light": "36px",
          "dark": "36px",
          "description": "Height of the field."
        },
        {
          "name": "--pct-field-height-lg",
          "type": "dimension",
          "ref": "{pct.control.height.lg}",
          "light": "44px",
          "dark": "44px",
          "description": "Height of the field at size lg."
        },
        {
          "name": "--pct-field-height-sm",
          "type": "dimension",
          "ref": "{pct.control.height.sm}",
          "light": "28px",
          "dark": "28px",
          "description": "Height of the field at size sm."
        },
        {
          "name": "--pct-field-hint-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the hint."
        },
        {
          "name": "--pct-field-label-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the label."
        },
        {
          "name": "--pct-field-label-gap",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Gap inside the label."
        },
        {
          "name": "--pct-field-padding-x",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of the field."
        },
        {
          "name": "--pct-field-padding-x-lg",
          "type": "dimension",
          "ref": "{pct.space.5}",
          "light": "16px",
          "dark": "16px",
          "description": "Horizontal padding of the field at size lg."
        },
        {
          "name": "--pct-field-padding-x-sm",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of the field at size sm."
        },
        {
          "name": "--pct-field-placeholder-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the placeholder."
        },
        {
          "name": "--pct-field-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the field."
        }
      ],
      "texts": [
        "fieldWarning",
        "numberMalformed"
      ],
      "keyboard": "| key                   | effect                                                                                                                                                                                                    | test                                       |\n| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |\n| `↑` / `↓`             | step by `step`                                                                                                                                                                                            | `apps/sandbox-e2e/src/number.spec.ts`      |\n| `PageUp` / `PageDown` | a larger step                                                                                                                                                                                             | `apps/sandbox-e2e/src/number.spec.ts`      |\n| typing                | the text **is not rewritten**, so the caret does not jump to the end                                                                                                                                      | `libs/components/field/src/number.spec.ts` |\n| `Enter` / `blur`      | commit: rounding and clamping to the bounds; text that is not a number is **kept** and named `Not a number` ([0070](../decisions/0070-what-the-control-knows-and-the-form-cannot-is-a-second-channel.md)) | `libs/components/field/src/number.spec.ts` |",
      "decisions": [
        {
          "id": "0009",
          "title": "The number field on type=\"text\""
        },
        {
          "id": "0003",
          "title": "Wrapper and control"
        },
        {
          "id": "0005",
          "title": "Signal forms without ControlValueAccessor"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 84.7,
          "killed": 238,
          "survived": 41,
          "mutants": 281,
          "files": 1
        },
        "e2e": {
          "cases": 10,
          "spec": "apps/sandbox-e2e/src/number.spec.ts"
        },
        "pairs": 0,
        "baselines": 2,
        "cost": {
          "elements": 13,
          "depth": 4,
          "listeners": 5,
          "renders": 1,
          "micros": 7634,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "date",
      "classes": [
        "PctDate"
      ],
      "role": "date field",
      "category": "Text & numbers",
      "status": "released",
      "entrypoint": "@pacit/components/date",
      "selectors": [
        "pct-date"
      ],
      "harnesses": [
        "PctDateHarness"
      ],
      "docs": "/components/date",
      "summary": "A date typed into a field or picked from a calendar the field opens.",
      "usage": {
        "code": "<pct-date label=\"Starts on\" [(value)]=\"startsOn\" />",
        "lang": "html"
      },
      "examples": [
        {
          "key": "bounds",
          "title": "Within bounds",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctDate, PctDay, pctAddDays, pctToday } from '@pacit/components/date';\n\n/**\n * Within bounds\n *\n * `min` and `max` are days, and the calendar greys what falls outside; typing a day\n * outside them marks the field invalid rather than silently moving it.\n */\n@Component({\n  selector: 'demo-date-bounds',\n  imports: [PctDate],\n  styles: ':host { display: grid; gap: 12px; max-inline-size: 24rem; }',\n  template: `\n    <pct-date\n      label=\"Delivery\"\n      hint=\"Within the next two weeks\"\n      [(value)]=\"delivery\"\n      [min]=\"today\"\n      [max]=\"latest\"\n    />\n    <p>Chosen: {{ delivery() ?? 'not yet' }}</p>\n  `,\n})\nexport class DateBoundsDemo {\n  readonly today = pctToday();\n  readonly latest = pctAddDays(this.today, 14);\n  readonly delivery = signal<PctDay | null>(null);\n}"
        }
      ],
      "api": [
        {
          "name": "PctDate",
          "kind": "component",
          "selector": "pct-date",
          "description": "Date field — a text control the library formats and parses per locale, with a calendar in a panel beside it.",
          "members": [
            {
              "name": "value",
              "kind": "model",
              "required": false,
              "type": "PctDay | null",
              "default": "null",
              "description": "The chosen day, or null when the field is empty.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "disabled",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Blocks the control and greys it — the native disabled, so it leaves the tab order as well. With [formField] the directive writes it, as it writes every input of the FormUiControl contract.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "readonly",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Keeps the value: the input takes the native readonly and the calendar button is disabled with it, while the field stays focusable.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "invalid",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "The form's verdict; shown only once touched, so an empty form does not open red.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "touched",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the user has left the field once; with invalid it gates the error face.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "required",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Marks the label with the required sign; with [formField] it follows the schema's required().",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "errors",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[]",
              "default": "[]",
              "description": "The form's validation errors; the first one's message takes the hint's place once the field is touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "warnings",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[] | undefined",
              "default": "undefined",
              "description": "A verdict without a veto (0087): shown after the error and before the hint once the field is touched, in the warning tone, with aria-invalid untouched. Left unbound, the control reads what pctWarn() wrote for its own [formField]; bound — [] included — the list given stands.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "name",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The native name — what a form submission calls the value.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "min",
              "kind": "input",
              "required": false,
              "type": "unknown",
              "default": "undefined",
              "description": "The earliest and latest day. They belong to the FormUiControl contract, which spells an absent bound undefined, and a signal form gives them through pctDayBounds — the compiler refuses the attribute beside [formField].",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "max",
              "kind": "input",
              "required": false,
              "type": "unknown",
              "default": "undefined",
              "description": "The latest day — as min, a bound on the calendar's walk and not a rewrite of what was typed.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "dateDisabled",
              "kind": "input",
              "required": false,
              "type": "PctDayPredicate | null",
              "default": "null",
              "description": "Days inside the bounds that still cannot be picked — weekends, holidays, taken slots.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "touch",
              "kind": "output",
              "required": false,
              "type": "void",
              "default": null,
              "description": "Emitted on blur — lets the form mark the field as touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "label",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The visible label, rendered by the control itself when it stands outside a pct-field; inside one, the field's label is the name.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "hint",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "A line of help under the control, outside a pct-field; the first error message takes its place while the field is invalid and touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name of a field with no visible label — an INPUT rather than an aria-label on the tag, because the textbox sits inside this template and an ARIA name on the roleless host is ignored.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabelledby",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "As ariaLabel, for a name that already stands somewhere on the page.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "locale",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "Overrides the application's LOCALE_ID for this field, as on [pctNumber].",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "firstDayOfWeek",
              "kind": "input",
              "required": false,
              "type": "number",
              "default": "0",
              "description": "Which day the week starts on, 1 (Monday) … 7 (Sunday). Absent, the locale decides.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "showFormat",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "true",
              "description": "Whether the format hint stands in the field while it is empty (dd.mm.yyyy).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "size",
              "kind": "input",
              "required": false,
              "type": "PctSize",
              "default": "config.defaultSize",
              "description": "Height 28 / 36 / 44 px — the axis every field shares; from providePctConfig by default (req-api-config).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "panelAlign",
              "kind": "input",
              "required": false,
              "type": "PctDatePanelAlign",
              "default": "'start'",
              "description": "Which edge of the field the panel lines up with.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-date",
              "note": ""
            },
            {
              "attribute": "data-pct-size",
              "kind": "attribute",
              "bound": "size()",
              "note": ""
            },
            {
              "attribute": "data-pct-open",
              "kind": "attribute",
              "bound": "open() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-invalid",
              "kind": "attribute",
              "bound": "showInvalid() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-warning",
              "kind": "attribute",
              "bound": "showWarning() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-disabled",
              "kind": "attribute",
              "bound": "disabled() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-malformed",
              "kind": "attribute",
              "bound": "malformed() ? \"\" : null",
              "note": "What the control knows and the form cannot: there is text in the field and it is not a date. See <code>malformed</code> below."
            },
            {
              "attribute": "data-pct-in-field",
              "kind": "attribute",
              "bound": "inField ? \"\" : null",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctCalendarCell",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "One cell of the grid, with everything the template and the reader need already decided.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDayPredicate",
          "kind": "type",
          "selector": null,
          "detail": "(day: PctDay) => boolean",
          "description": "A day the consumer refuses — weekends, holidays, a taken slot.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctCalendar",
          "kind": "component",
          "selector": "pct-calendar",
          "detail": null,
          "description": "Calendar — one month of days as a role=\"grid\", walked by a roving tabindex.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDatePanelAlign",
          "kind": "type",
          "selector": null,
          "detail": "'start' | 'end'",
          "description": "Which edge of the field the panel lines its own up with.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDate",
          "kind": "component",
          "selector": "pct-date",
          "detail": null,
          "description": "Date field — a text control the library formats and parses per locale, with a calendar in a panel beside it.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDayBound",
          "kind": "type",
          "selector": null,
          "detail": "PctDay | undefined | LogicFn<TValue, PctDay | undefined, TPathKind>",
          "description": "A bound pctDayBounds takes: a day, or a function of the field's context that returns one — the two shapes Angular's own min() takes. One that is not a day, undefined among them, is no bound, as it is on the field's input.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctDayBoundError",
          "kind": "type",
          "selector": null,
          "detail": "| { readonly kind: 'minDay'",
          "description": "What pctDayBounds reports: minDay for a day before min, maxDay for one after max. Each carries the bound it broke, as the bound was written.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctDayBoundsConfig",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The bounds of pctDayBounds, and the sentence its errors carry — one for both, or one written from the error, which holds the bound.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctDayBounds",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Bounds a date field in a signal form: what Angular's minDate() and maxDate() do for a Date, for a PctDay — which they cannot take (their path is a Date's, and their validator reads the value with getTime()), and which the compiler refuses as an attribute beside [formField] (NG8022). The bounds reach the field's min / max, so the calendar's walk stops at them, and the form grades the value: a day outside is minDay or maxDay.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctDay",
          "kind": "type",
          "selector": null,
          "detail": "string",
          "description": "A calendar day, written the way the platform already writes one: YYYY-MM-DD.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDayParts",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The three fields of a day, as numbers. month is 1–12, the way a person says it.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctDaysInMonth",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "How many days February has — the Gregorian rule, written once.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "isPctDay",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Whether a string is a day this library will work with: the right shape and a day the calendar has. 2026-02-30 has the shape and is not a day, and the difference matters — a value read from a server, a query string or a localStorage entry is a string somebody else wrote.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctDay",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Builds a day from its three fields, normalising an overflow the way a person expects — month 13 is January of the next year, day 0 is the last day of the previous month. That is what makes pctAddMonths and pctAddDays one line each instead of three branches.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctDayAsUtc",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The day as the one Date anything outside this file is allowed to see: midnight UTC, which is what Intl.DateTimeFormat with timeZone: 'UTC' reads back as the same three fields it was built from. Handing out a local-time Date is the defect this whole module exists to refuse.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctDayParts",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The three fields of a day. The caller has already established that it is one.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctToday",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Today, read from the LOCAL clock — the one read of local time in this file, and the only one that belongs: \"what day is it\" is a question about where the user is standing, and every other question here is arithmetic on a day that already has an answer.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctAddDays",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "n days on (or back).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctAddMonths",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "n months on (or back), clamped to the length of the month it lands in: 31 January plus one month is 28 February and not 3 March. The clamp is what a calendar's \"next month\" button means — a walk that changed the day of the month would move the cursor twice.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctCompareDays",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "-1, 0 or 1 — read from the three fields, though the shape invites a string compare and carried one for months.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctWeekday",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The ISO weekday: Monday is 1, Sunday is 7 — the numbering Intl.Locale.prototype.getWeekInfo() answers in, so the two never need converting between them.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctClampDay",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Held inside the bounds, either of which may be absent — and undefined is what absent is, because that is how the FormUiControl contract's min / max spell an absent bound.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctMonthGrid",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The grid of one month: whole weeks, starting on firstDayOfWeek, with the days of the neighbouring months that share those weeks. Six weeks always, and that is a decision rather than an oversight — a month spans four to six weeks depending on where it starts, and a panel that changed height between March and August would move the page under the pointer mid-walk.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDayField",
          "kind": "type",
          "selector": null,
          "detail": "'day' | 'month' | 'year'",
          "description": "Which of the three fields a position in the format holds.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDayLetters",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The letters a format hint is written with — one per field, from PCT_TEXTS.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctFirstDayOfWeek",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Which day the week starts on, 1 (Monday) … 7 (Sunday).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDayFormat",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "Everything about writing and reading a day in one language, built once per locale.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctDayFormat",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The day, weekday and month names of a locale, built once and shared.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctWeekdayName",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The two names a column header needs: the one the eye reads and the one a reader says.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctWeekdayNames",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The seven column headings, rotated so that the first is firstDayOfWeek.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctMonthCaption",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The month and year over the grid (\"December 2026\"), in the value's own calendar.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "caption",
          "description": "the month and the year over the grid"
        },
        {
          "name": "day",
          "description": "one day cell"
        },
        {
          "name": "grid",
          "description": "the grid of days"
        },
        {
          "name": "nav",
          "description": "the previous-month and next-month buttons"
        },
        {
          "name": "week",
          "description": "one row of the grid"
        },
        {
          "name": "weekday",
          "description": "a heading over a column of days"
        },
        {
          "name": "control",
          "description": "the typed input"
        },
        {
          "name": "error",
          "description": "the message when the value is invalid"
        },
        {
          "name": "hint",
          "description": "the hint under the input"
        },
        {
          "name": "label",
          "description": "the label of the typed input"
        },
        {
          "name": "panel",
          "description": "the calendar, floating or inline"
        },
        {
          "name": "toggle",
          "description": "the button that opens the calendar"
        },
        {
          "name": "warning",
          "description": "the message when the value is allowed and suspect — after the error, before the hint (0087)"
        }
      ],
      "tokens": [
        {
          "name": "--pct-date-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the date field."
        },
        {
          "name": "--pct-date-bg-disabled",
          "type": "color",
          "ref": "{pct.surface-disabled}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the date field while disabled."
        },
        {
          "name": "--pct-date-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the date field."
        },
        {
          "name": "--pct-date-border-disabled",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge colour of the date field while disabled."
        },
        {
          "name": "--pct-date-border-focus",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge colour of the date field when focused."
        },
        {
          "name": "--pct-date-border-hover",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Edge colour of the date field on hover."
        },
        {
          "name": "--pct-date-border-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Edge colour of the date field when invalid."
        },
        {
          "name": "--pct-date-border-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Edge colour of the date field under a warning (0087)."
        },
        {
          "name": "--pct-date-caption-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of the month caption."
        },
        {
          "name": "--pct-date-caption-font-weight",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of the month caption."
        },
        {
          "name": "--pct-date-day-bg-hover",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of a day cell on hover."
        },
        {
          "name": "--pct-date-day-bg-selected",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Background of a day cell when selected."
        },
        {
          "name": "--pct-date-day-border-today",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge colour of a day cell for today."
        },
        {
          "name": "--pct-date-day-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of a day cell while disabled."
        },
        {
          "name": "--pct-date-day-fg-muted",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of a day outside the month shown."
        },
        {
          "name": "--pct-date-day-fg-selected",
          "type": "color",
          "ref": "{pct.on-primary}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Text colour of a day cell when selected."
        },
        {
          "name": "--pct-date-day-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of a day cell."
        },
        {
          "name": "--pct-date-day-font-size-lg",
          "type": "dimension",
          "ref": "{pct.font.size.lg}",
          "light": "16px",
          "dark": "16px",
          "description": "Font size of a day cell at size lg."
        },
        {
          "name": "--pct-date-day-font-size-sm",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of a day cell at size sm."
        },
        {
          "name": "--pct-date-day-font-weight-selected",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of a day cell when selected."
        },
        {
          "name": "--pct-date-day-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of a day cell."
        },
        {
          "name": "--pct-date-day-size",
          "type": "dimension",
          "ref": null,
          "light": "32px",
          "dark": "32px",
          "description": "Size of a day cell."
        },
        {
          "name": "--pct-date-day-size-lg",
          "type": "dimension",
          "ref": null,
          "light": "36px",
          "dark": "36px",
          "description": "Size of a day cell at size lg."
        },
        {
          "name": "--pct-date-day-size-sm",
          "type": "dimension",
          "ref": null,
          "light": "28px",
          "dark": "28px",
          "description": "Size of a day cell at size sm."
        },
        {
          "name": "--pct-date-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the date field."
        },
        {
          "name": "--pct-date-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of the date field while disabled."
        },
        {
          "name": "--pct-date-fg-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Text colour of the date field when invalid."
        },
        {
          "name": "--pct-date-fg-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Text colour of the warning line (0087)."
        },
        {
          "name": "--pct-date-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of the date field."
        },
        {
          "name": "--pct-date-font-size-lg",
          "type": "dimension",
          "ref": "{pct.font.size.lg}",
          "light": "16px",
          "dark": "16px",
          "description": "Font size of the date field at size lg."
        },
        {
          "name": "--pct-date-font-size-sm",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of the date field at size sm."
        },
        {
          "name": "--pct-date-gap",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Gap between the label, the input and the hint."
        },
        {
          "name": "--pct-date-hint-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the hint."
        },
        {
          "name": "--pct-date-icon-size",
          "type": "dimension",
          "ref": null,
          "light": "16px",
          "dark": "16px",
          "description": "Size of the icon."
        },
        {
          "name": "--pct-date-label-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the label."
        },
        {
          "name": "--pct-date-nav-bg-hover",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the month navigation on hover."
        },
        {
          "name": "--pct-date-nav-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the month navigation."
        },
        {
          "name": "--pct-date-nav-size",
          "type": "dimension",
          "ref": null,
          "light": "28px",
          "dark": "28px",
          "description": "Size of the month navigation."
        },
        {
          "name": "--pct-date-padding-x",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of the date field."
        },
        {
          "name": "--pct-date-padding-x-lg",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of the date field at size lg."
        },
        {
          "name": "--pct-date-padding-x-sm",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of the date field at size sm."
        },
        {
          "name": "--pct-date-padding-y",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Vertical padding of the date field."
        },
        {
          "name": "--pct-date-padding-y-lg",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Vertical padding of the date field at size lg."
        },
        {
          "name": "--pct-date-padding-y-sm",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Vertical padding of the date field at size sm."
        },
        {
          "name": "--pct-date-panel-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the panel."
        },
        {
          "name": "--pct-date-panel-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the panel."
        },
        {
          "name": "--pct-date-panel-padding-x",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of the panel."
        },
        {
          "name": "--pct-date-panel-padding-y",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Vertical padding of the panel."
        },
        {
          "name": "--pct-date-panel-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the panel."
        },
        {
          "name": "--pct-date-panel-shadow",
          "type": "shadow",
          "ref": "{pct.shadow-panel}",
          "light": "0 8px 24px rgba(15, 23, 42, 0.14)",
          "dark": "0 8px 24px rgba(0, 0, 0, 0.55)",
          "description": "Shadow of the panel."
        },
        {
          "name": "--pct-date-placeholder-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the placeholder."
        },
        {
          "name": "--pct-date-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the date field."
        },
        {
          "name": "--pct-date-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Touch-target floor of the date field."
        },
        {
          "name": "--pct-date-toggle-bg-hover",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the calendar toggle on hover."
        },
        {
          "name": "--pct-date-toggle-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the calendar toggle."
        },
        {
          "name": "--pct-date-toggle-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the calendar toggle."
        },
        {
          "name": "--pct-date-toggle-size",
          "type": "dimension",
          "ref": null,
          "light": "20px",
          "dark": "20px",
          "description": "Size of the calendar toggle."
        },
        {
          "name": "--pct-date-weekday-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of a weekday heading."
        },
        {
          "name": "--pct-date-weekday-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of a weekday heading."
        },
        {
          "name": "--pct-date-weekday-font-weight",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of a weekday heading."
        }
      ],
      "texts": [
        "dateDayLetter",
        "dateMalformed",
        "dateMonthLetter",
        "dateNextMonth",
        "dateOpen",
        "datePreviousMonth",
        "dateYearLetter",
        "fieldWarning"
      ],
      "keyboard": "| key                    | effect                                            | test                                |\n| ---------------------- | ------------------------------------------------- | ----------------------------------- |\n| any text               | typed into the field, parsed on commit            | `apps/sandbox-e2e/src/date.spec.ts` |\n| `Tab`                  | field → calendar button → out                     | native                              |\n| `Enter` on the button  | opens the panel, focus lands on the cursor's cell | `apps/sandbox-e2e/src/date.spec.ts` |\n| `Escape` in the panel  | closes it, focus back to the field                | `apps/sandbox-e2e/src/date.spec.ts` |\n| `Tab` out of the panel | closes it, focus **spliced back** onto the field  | `apps/sandbox-e2e/src/date.spec.ts` |\n\nThe grid's own map is on [`PctCalendar`](./calendar.md). The field itself adds **no** key of\nits own: what a user types is text, and the platform's own editing keys are the whole of it.",
      "decisions": [
        {
          "id": "0043",
          "title": "A day is not an instant, and the field that takes one is text the application formats"
        },
        {
          "id": "0093",
          "title": "A bound of the field's own type reaches it through the platform's limit key, and the module ships the rule"
        },
        {
          "id": "0035",
          "title": "A filter is a question, not a value"
        },
        {
          "id": "0031",
          "title": "A panel's tab order belongs to its trigger"
        },
        {
          "id": "0025",
          "title": "A panel says whether it takes focus, and the rest of the focus layer waits for a consumer"
        },
        {
          "id": "0009",
          "title": "The number field on type=\"text\""
        },
        {
          "id": "0005",
          "title": "Signal forms without ControlValueAccessor"
        },
        {
          "id": "0003",
          "title": "Wrapper and control"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 80.15,
          "killed": 218,
          "survived": 54,
          "mutants": 272,
          "files": 1
        },
        "e2e": {
          "cases": 20,
          "spec": "apps/sandbox-e2e/src/date.spec.ts"
        },
        "pairs": 26,
        "baselines": 3,
        "cost": {
          "elements": 10,
          "depth": 6,
          "listeners": 4,
          "renders": 1,
          "micros": 6352,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "time",
      "classes": [
        "PctTime"
      ],
      "role": "time field",
      "category": "Text & numbers",
      "status": "released",
      "entrypoint": "@pacit/components/time",
      "selectors": [
        "pct-time"
      ],
      "harnesses": [
        "PctTimeHarness"
      ],
      "docs": "/components/time",
      "summary": "A time of day typed into a field or picked from columns the field opens.",
      "usage": {
        "code": "<pct-time label=\"Starts at\" [(value)]=\"startsAt\" />",
        "lang": "html"
      },
      "examples": [
        {
          "key": "locales",
          "title": "The clock is the language's",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctTime, PctTimeOfDay } from '@pacit/components/time';\n\n/**\n * The clock is the language's\n *\n * One value on three clocks: twelve hours in American English, twenty-four in British, and the\n * day period before the hour in Korean — all of it from `Intl` for the field's `locale`, and a\n * clock forced on a reader is a locale too (`en-US-u-hc-h23`).\n */\n@Component({\n  selector: 'demo-time-locales',\n  imports: [PctTime],\n  styles: ':host { display: grid; gap: 12px; max-inline-size: 24rem; }',\n  template: `\n    <pct-time label=\"American English\" locale=\"en-US\" [(value)]=\"time\" />\n    <pct-time label=\"British English\" locale=\"en-GB\" [(value)]=\"time\" />\n    <pct-time label=\"Korean\" locale=\"ko-KR\" [(value)]=\"time\" />\n  `,\n})\nexport class TimeLocalesDemo {\n  readonly time = signal<PctTimeOfDay | null>('13:05');\n}"
        },
        {
          "key": "step",
          "title": "The step, and seconds",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctTime, PctTimeOfDay } from '@pacit/components/time';\n\n/**\n * The step, and seconds\n *\n * `step` is in seconds, as the native attribute's: `900` lists the quarters in the panel, and a\n * step under a minute adds a column of seconds and puts them in the value. A time typed off the\n * step is kept — it is the form's to refuse, with `pctTimeOnStep`.\n */\n@Component({\n  selector: 'demo-time-step',\n  imports: [PctTime],\n  styles: ':host { display: grid; gap: 12px; max-inline-size: 24rem; }',\n  template: `\n    <pct-time label=\"Quarter hours\" step=\"900\" [(value)]=\"quarter\" />\n    <pct-time label=\"To the second\" step=\"1\" [(value)]=\"precise\" />\n    <p>Values: {{ quarter() ?? 'none' }} · {{ precise() ?? 'none' }}</p>\n  `,\n})\nexport class TimeStepDemo {\n  readonly quarter = signal<PctTimeOfDay | null>('09:15');\n  readonly precise = signal<PctTimeOfDay | null>('09:15:30');\n}"
        },
        {
          "key": "bounds",
          "title": "Within bounds",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctTime, PctTimeOfDay } from '@pacit/components/time';\n\n/**\n * Within bounds\n *\n * `min` and `max` are times, and the panel disables the rows outside them; a `min` later than\n * `max` is a window across midnight. A time typed outside them is kept and left to the form.\n */\n@Component({\n  selector: 'demo-time-bounds',\n  imports: [PctTime],\n  styles: ':host { display: grid; gap: 12px; max-inline-size: 24rem; }',\n  template: `\n    <pct-time\n      label=\"Office hours\"\n      min=\"09:00\"\n      max=\"17:00\"\n      step=\"900\"\n      [(value)]=\"office\"\n    />\n    <pct-time\n      label=\"The night shift\"\n      min=\"22:00\"\n      max=\"06:00\"\n      step=\"1800\"\n      [(value)]=\"night\"\n    />\n  `,\n})\nexport class TimeBoundsDemo {\n  readonly office = signal<PctTimeOfDay | null>(null);\n  readonly night = signal<PctTimeOfDay | null>(null);\n}"
        }
      ],
      "api": [
        {
          "name": "PctTime",
          "kind": "component",
          "selector": "pct-time",
          "description": "Time field — a text control the library formats and parses per locale, with columns of hours and minutes in a panel beside it.",
          "members": [
            {
              "name": "value",
              "kind": "model",
              "required": false,
              "type": "PctTimeOfDay | null",
              "default": "null",
              "description": "The chosen time, or null when the field is empty.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "disabled",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Blocks the control and greys it — the native disabled, so it leaves the tab order as well. With [formField] the directive writes it, as it writes every input of the FormUiControl contract.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "readonly",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Keeps the value: the input takes the native readonly and the clock button is disabled with it, while the field stays focusable.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "invalid",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "The form's verdict; shown only once touched, so an empty form does not open red.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "touched",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the user has left the field once; with invalid it gates the error face.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "required",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Marks the label with the required sign; with [formField] it follows the schema's required().",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "errors",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[]",
              "default": "[]",
              "description": "The form's validation errors; the first one's message takes the hint's place once the field is touched.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "warnings",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[] | undefined",
              "default": "undefined",
              "description": "A verdict without a veto (0087): shown after the error and before the hint once the field is touched, in the warning tone, with aria-invalid untouched. Left unbound, the control reads what pctWarn() wrote for its own [formField]; bound — [] included — the list given stands.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "name",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The native name — what a form submission calls the value.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "min",
              "kind": "input",
              "required": false,
              "type": "unknown",
              "default": "undefined",
              "description": "The earliest time. It belongs to the FormUiControl contract, which spells an absent bound undefined, and a signal form gives it through pctTimeBounds — the compiler refuses the attribute beside [formField].",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "max",
              "kind": "input",
              "required": false,
              "type": "unknown",
              "default": "undefined",
              "description": "The latest time — as min, a bound on the panel's walk and not a rewrite of what was typed.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "step",
              "kind": "input",
              "required": false,
              "type": "number",
              "default": "60",
              "description": "The step in seconds, counted from min — 60 by default, like the native attribute. It decides what the panel's columns offer (900 is the minutes 00 15 30 45), whether a seconds column exists and the value carries :ss — and nothing about typed text: a time off the step is a time, and the form's to refuse (0086 §5). A step the columns cannot list is refused with a dev-mode warning and read as 60.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "touch",
              "kind": "output",
              "required": false,
              "type": "void",
              "default": null,
              "description": "Emitted on blur — lets the form mark the field as touched.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "label",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The visible label, rendered by the control itself when it stands outside a pct-field; inside one, the field's label is the name.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "hint",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "A line of help under the control, outside a pct-field; the first error message takes its place while the field is invalid and touched.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name of a field with no visible label — an INPUT rather than an aria-label on the tag, because the textbox sits inside this template and an ARIA name on the roleless host is ignored.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "ariaLabelledby",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "As ariaLabel, for a name that already stands somewhere on the page.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "locale",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "Overrides the application's LOCALE_ID for this field — and with it the clock: en-US counts twelve hours and en-GB twenty-four, and en-US-u-hc-h23 forces the second on the first.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "showFormat",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "true",
              "description": "Whether the format hint stands in the field while it is empty (hh:mm).",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "size",
              "kind": "input",
              "required": false,
              "type": "PctSize",
              "default": "config.defaultSize",
              "description": "Height 28 / 36 / 44 px — the axis every field shares; from providePctConfig by default (req-api-config).",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "panelAlign",
              "kind": "input",
              "required": false,
              "type": "PctTimePanelAlign",
              "default": "'start'",
              "description": "Which edge of the field the panel lines up with.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-time",
              "note": ""
            },
            {
              "attribute": "data-pct-size",
              "kind": "attribute",
              "bound": "size()",
              "note": ""
            },
            {
              "attribute": "data-pct-open",
              "kind": "attribute",
              "bound": "open() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-invalid",
              "kind": "attribute",
              "bound": "showInvalid() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-warning",
              "kind": "attribute",
              "bound": "showWarning() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-disabled",
              "kind": "attribute",
              "bound": "disabled() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-malformed",
              "kind": "attribute",
              "bound": "malformed() ? \"\" : null",
              "note": "What the control knows and the form cannot: there is text in the field and it is not a time. See <code>malformed</code> below."
            },
            {
              "attribute": "data-pct-in-field",
              "kind": "attribute",
              "bound": "inField ? \"\" : null",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctHourCycle",
          "kind": "type",
          "selector": null,
          "detail": "'h11' | 'h12' | 'h23' | 'h24'",
          "description": "The four clocks Intl names. h11 and h12 are twelve-hour clocks that write the first hour 0 and 12; h23 and h24 are twenty-four-hour clocks that write it 0 and 24. The value is 00:05 under all four — a cycle is how a time is written, never which time it is.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeField",
          "kind": "type",
          "selector": null,
          "detail": "'hour' | 'minute' | 'second' | 'dayPeriod'",
          "description": "Which part of the written time a position holds.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeLetters",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The letters a format hint is written with — one per field, from PCT_TEXTS, because they are words of the application's language and not facts about the locale (0086 §3).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeFormat",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "Everything about writing and reading a time in one language, built once per locale.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctTimeFormat",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "How a time is written and read in one language, built once and shared.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeOfDay",
          "kind": "type",
          "selector": null,
          "detail": "string",
          "description": "A time of day, written the way the platform already writes one: HH:mm, or HH:mm:ss where the field's step has seconds in it.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeOfDayParts",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The three fields of a time, as numbers on a 24-hour clock. second is 0 for a time written without seconds — the two shapes are one time.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "isPctTimeOfDay",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Whether a string is a time this library will work with: 00:00 to 23:59:59, two digits a field. A value read from a server, a query string or localStorage is a string somebody else wrote, and three of the strings they write are refused on purpose:",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctTimeOfDay",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Builds a time from its fields: HH:mm, or HH:mm:ss when second is given — 0 included, because the shape is the caller's to choose.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctTimeOfDayParts",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The three fields of a time. The caller has already established that it is one.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctNow",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The time on the LOCAL clock — the one read of local time in this file, and the only one that belongs: \"what time is it\" is a question about where the user is standing, and every other question here is arithmetic on a time that already has an answer.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctAddSeconds",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "n seconds on (or back), and the clock comes round: 23:59:30 plus 45 seconds is 00:00:15. The result keeps the shape it was given unless it has seconds that shape cannot carry — 13:05 plus an hour is 14:05, plus 30 seconds is 13:05:30.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctAddMinutes",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "n minutes on (or back), and the clock comes round: 23:30 plus 45 minutes is 00:15. The shape is kept — a whole minute never moves the seconds.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctCompareTimes",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "-1, 0 or 1, read as seconds since midnight — so 13:05 and 13:05:00, one time in two shapes, compare equal.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctClampTime",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Held inside the bounds, either of which may be absent — and undefined is what absent is, because that is what the FormUiControl contract's min / max are.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "isPctTimeStep",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Whether step — in seconds, like the native attribute — is one the columns can list: a whole number of seconds that divides a minute, a whole number of minutes that divides an hour, or a whole number of hours that divides a day. Any other — 420 seconds, seven-minute slots — makes the valid minutes depend on the hour, which columns that are fields cannot show (0086 §5).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctTimeOnStep",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Whether a time stands on the step, counted from base — the field's min when it has one, as the native element counts (0086, A11).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctSnapToStep",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The time on the step nearest to this one, counted from base, with the clock coming round: at an hour step 23:40 is 00:00. Exactly halfway goes up, as Math.round does. The shape is kept unless the step puts seconds where it had none.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeRow",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "One row of a column, with everything the template and the walk need already decided.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeColumn",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "One column — one field of the time, one role=\"listbox\", one tab stop.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimePredicate",
          "kind": "type",
          "selector": null,
          "detail": "(time: PctTimeOfDay) => boolean",
          "description": "A predicate on a time of day: true refuses it. The columns ask it of every time on the step, once per change of the step, the bounds or the predicate itself.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeColumns",
          "kind": "component",
          "selector": "pct-time-columns",
          "detail": null,
          "description": "Time columns — one role=\"listbox\" per field of a time, side by side: hours and minutes, seconds where the step has them, and the two halves of the day where the clock has twelve hours (0086 §4).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimePanelAlign",
          "kind": "type",
          "selector": null,
          "detail": "'start' | 'end'",
          "description": "Which edge of the field the panel lines its own up with.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTime",
          "kind": "component",
          "selector": "pct-time",
          "detail": null,
          "description": "Time field — a text control the library formats and parses per locale, with columns of hours and minutes in a panel beside it.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeBound",
          "kind": "type",
          "selector": null,
          "detail": "| PctTimeOfDay | undefined | LogicFn<TValue, PctTimeOfDay | undefined, TPathKind>",
          "description": "A bound pctTimeBounds takes: a time, or a function of the field's context that returns one — the two shapes Angular's own min() takes. One that is not a time, undefined among them, is no bound, as it is on the field's input.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeBoundError",
          "kind": "type",
          "selector": null,
          "detail": "| { readonly kind: 'minTime'",
          "description": "What pctTimeBounds reports: minTime for a time before min, maxTime for one after max — and both at once for a time in the gap of a window across midnight, which is the native element's reading, \"under and over\" (0086, A12). Each carries the bound it broke, as the bound was written.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeBoundsConfig",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The bounds of pctTimeBounds, and the sentence its errors carry — one for both, or one written from the error, which holds the bound.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctTimeBounds",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Bounds a time field in a signal form: what Angular's min() and max() do for a number, for a PctTimeOfDay — which they cannot take, and which the compiler refuses as an attribute beside [formField] (NG8022). The bounds reach the field's min / max, so the panel's walk stops at them, and the form grades the value: a time outside is minTime or maxTime, and a time in the gap of a window across midnight — min later than max, 22:00 to 06:00 — is both, as the native element reads it (0086, A12).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        }
      ],
      "parts": [
        {
          "name": "control",
          "description": "the typed input"
        },
        {
          "name": "error",
          "description": "the message when the value is invalid"
        },
        {
          "name": "hint",
          "description": "the hint under the input"
        },
        {
          "name": "label",
          "description": "the label of the typed input"
        },
        {
          "name": "panel",
          "description": "the dialog the columns stand in"
        },
        {
          "name": "toggle",
          "description": "the button that opens the columns"
        },
        {
          "name": "warning",
          "description": "the message when the value is allowed and suspect — after the error, before the hint (0087)"
        },
        {
          "name": "column",
          "description": "one column of the panel — a listbox of one field of the time"
        },
        {
          "name": "option",
          "description": "one row of a column"
        }
      ],
      "tokens": [
        {
          "name": "--pct-time-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the time field."
        },
        {
          "name": "--pct-time-bg-disabled",
          "type": "color",
          "ref": "{pct.surface-disabled}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the time field while disabled."
        },
        {
          "name": "--pct-time-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the time field."
        },
        {
          "name": "--pct-time-border-disabled",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge colour of the time field while disabled."
        },
        {
          "name": "--pct-time-border-focus",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge colour of the time field when focused."
        },
        {
          "name": "--pct-time-border-hover",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Edge colour of the time field on hover."
        },
        {
          "name": "--pct-time-border-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Edge colour of the time field when invalid."
        },
        {
          "name": "--pct-time-border-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Edge colour of the time field under a warning (0087)."
        },
        {
          "name": "--pct-time-column-height",
          "type": "dimension",
          "ref": null,
          "light": "224px",
          "dark": "224px",
          "description": "Height of a column."
        },
        {
          "name": "--pct-time-column-min-width",
          "type": "dimension",
          "ref": null,
          "light": "56px",
          "dark": "56px",
          "description": "Minimum width of a column — a day-period word wider than two digits widens its own column."
        },
        {
          "name": "--pct-time-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the time field."
        },
        {
          "name": "--pct-time-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of the time field while disabled."
        },
        {
          "name": "--pct-time-fg-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Text colour of the time field when invalid."
        },
        {
          "name": "--pct-time-fg-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Text colour of the warning line (0087)."
        },
        {
          "name": "--pct-time-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of the time field."
        },
        {
          "name": "--pct-time-font-size-lg",
          "type": "dimension",
          "ref": "{pct.font.size.lg}",
          "light": "16px",
          "dark": "16px",
          "description": "Font size of the time field at size lg."
        },
        {
          "name": "--pct-time-font-size-sm",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of the time field at size sm."
        },
        {
          "name": "--pct-time-hint-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the hint."
        },
        {
          "name": "--pct-time-icon-size",
          "type": "dimension",
          "ref": null,
          "light": "16px",
          "dark": "16px",
          "description": "Size of the icon."
        },
        {
          "name": "--pct-time-label-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the label."
        },
        {
          "name": "--pct-time-option-bg-hover",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of a row on hover."
        },
        {
          "name": "--pct-time-option-bg-selected",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Background of a row when selected."
        },
        {
          "name": "--pct-time-option-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of a row while disabled."
        },
        {
          "name": "--pct-time-option-fg-selected",
          "type": "color",
          "ref": "{pct.on-primary}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Text colour of a row when selected."
        },
        {
          "name": "--pct-time-option-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of a row."
        },
        {
          "name": "--pct-time-option-font-size-lg",
          "type": "dimension",
          "ref": "{pct.font.size.lg}",
          "light": "16px",
          "dark": "16px",
          "description": "Font size of a row at size lg."
        },
        {
          "name": "--pct-time-option-font-size-sm",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of a row at size sm."
        },
        {
          "name": "--pct-time-option-font-weight-selected",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of a row when selected."
        },
        {
          "name": "--pct-time-option-height",
          "type": "dimension",
          "ref": null,
          "light": "32px",
          "dark": "32px",
          "description": "Height of a row."
        },
        {
          "name": "--pct-time-option-height-lg",
          "type": "dimension",
          "ref": null,
          "light": "36px",
          "dark": "36px",
          "description": "Height of a row at size lg."
        },
        {
          "name": "--pct-time-option-height-sm",
          "type": "dimension",
          "ref": null,
          "light": "28px",
          "dark": "28px",
          "description": "Height of a row at size sm."
        },
        {
          "name": "--pct-time-option-padding-x",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of a row."
        },
        {
          "name": "--pct-time-option-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of a row."
        },
        {
          "name": "--pct-time-padding-x",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of the time field."
        },
        {
          "name": "--pct-time-padding-x-lg",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of the time field at size lg."
        },
        {
          "name": "--pct-time-padding-x-sm",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of the time field at size sm."
        },
        {
          "name": "--pct-time-padding-y",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Vertical padding of the time field."
        },
        {
          "name": "--pct-time-padding-y-lg",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Vertical padding of the time field at size lg."
        },
        {
          "name": "--pct-time-padding-y-sm",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Vertical padding of the time field at size sm."
        },
        {
          "name": "--pct-time-panel-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the panel."
        },
        {
          "name": "--pct-time-panel-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the panel."
        },
        {
          "name": "--pct-time-panel-gap",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Gap between the columns of the panel."
        },
        {
          "name": "--pct-time-panel-padding-x",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of the panel."
        },
        {
          "name": "--pct-time-panel-padding-y",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Vertical padding of the panel."
        },
        {
          "name": "--pct-time-panel-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the panel."
        },
        {
          "name": "--pct-time-panel-shadow",
          "type": "shadow",
          "ref": "{pct.shadow-panel}",
          "light": "0 8px 24px rgba(15, 23, 42, 0.14)",
          "dark": "0 8px 24px rgba(0, 0, 0, 0.55)",
          "description": "Shadow of the panel."
        },
        {
          "name": "--pct-time-placeholder-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the placeholder."
        },
        {
          "name": "--pct-time-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the time field."
        },
        {
          "name": "--pct-time-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Touch-target floor of the time field."
        },
        {
          "name": "--pct-time-toggle-bg-hover",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the clock toggle on hover."
        },
        {
          "name": "--pct-time-toggle-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the clock toggle."
        },
        {
          "name": "--pct-time-toggle-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the clock toggle."
        },
        {
          "name": "--pct-time-toggle-size",
          "type": "dimension",
          "ref": null,
          "light": "20px",
          "dark": "20px",
          "description": "Size of the clock toggle."
        }
      ],
      "texts": [
        "fieldWarning",
        "timeHourLetter",
        "timeHours",
        "timeMalformed",
        "timeMinuteLetter",
        "timeMinutes",
        "timeOpen",
        "timePeriod",
        "timeSecondLetter",
        "timeSeconds"
      ],
      "keyboard": "| key                    | effect                                                            | test                                                                               |\n| ---------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------- |\n| any text               | typed into the field, parsed on commit                            | `apps/sandbox-e2e/src/time.spec.ts`                                                |\n| `Tab`                  | field → clock button → out                                        | native                                                                             |\n| `Enter` on the button  | opens the panel, focus lands on the hour column at the held time  | `apps/sandbox-e2e/src/time.spec.ts`                                                |\n| `Tab` in the panel     | the next column — one stop each — and off the last, out           | `apps/sandbox-e2e/src/time.spec.ts`                                                |\n| `Enter` in a column    | takes the time and closes the panel, focus back to the field      | `apps/sandbox-e2e/src/time.spec.ts`                                                |\n| `Escape` in the panel  | closes it, focus back to the field, the value as the columns left | `apps/sandbox-e2e/src/time.spec.ts`, `libs/components/time/src/time-field.spec.ts` |\n| `Tab` out of the panel | closes it, focus **spliced back** onto the field                  | `apps/sandbox-e2e/src/time.spec.ts`                                                |\n\nThe columns' own map is on [`PctTimeColumns`](./columns.md). The field itself adds **no**\nkey of its own: what a user types is text, and the platform's own editing keys are the whole\nof it — the arrows do not step the segment under the caret, because the engines step it three\nways (0086, A13 and A14) and a reader is never told a textbox has a value to step.",
      "decisions": [
        {
          "id": "0086",
          "title": "A time of day is a wall clock, and the field that takes one is text the application formats"
        },
        {
          "id": "0093",
          "title": "A bound of the field's own type reaches it through the platform's limit key, and the module ships the rule"
        },
        {
          "id": "0070",
          "title": "What the control knows and the form cannot is a second channel"
        },
        {
          "id": "0035",
          "title": "A filter is a question, not a value"
        },
        {
          "id": "0031",
          "title": "A panel's tab order belongs to its trigger"
        },
        {
          "id": "0025",
          "title": "A panel says whether it takes focus, and the rest of the focus layer waits for a consumer"
        },
        {
          "id": "0024",
          "title": "The overlay layer carries what an overlay severs, and borrows the closing stack"
        },
        {
          "id": "0022",
          "title": "One message line, and it is the error that takes it"
        },
        {
          "id": "0005",
          "title": "Signal forms without ControlValueAccessor"
        },
        {
          "id": "0003",
          "title": "Wrapper and control"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 86.41,
          "killed": 318,
          "survived": 50,
          "mutants": 368,
          "files": 1
        },
        "e2e": {
          "cases": 23,
          "spec": "apps/sandbox-e2e/src/time.spec.ts"
        },
        "pairs": 23,
        "baselines": 3,
        "cost": {
          "elements": 10,
          "depth": 6,
          "listeners": 4,
          "renders": 1,
          "micros": 5599,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "datetime",
      "classes": [
        "PctDatetime"
      ],
      "role": "datetime field",
      "category": "Text & numbers",
      "status": "released",
      "entrypoint": "@pacit/components/datetime",
      "selectors": [
        "pct-datetime"
      ],
      "harnesses": [
        "PctDatetimeHarness"
      ],
      "docs": "/components/datetime",
      "summary": "A day and a time of it, typed into two texts under one label or picked in one panel holding a calendar and columns of hours and minutes.",
      "usage": {
        "code": "<pct-datetime label=\"Starts\" [(value)]=\"startsAt\" />",
        "lang": "html"
      },
      "examples": [
        {
          "key": "locales",
          "title": "In other languages",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctDateTime, PctDatetime } from '@pacit/components/datetime';\n\n/**\n * In other languages\n *\n * Which half stands first, the day's order and the clock all come from Intl for the field's\n * locale — the time first in Vietnamese. One value, written three ways.\n */\n@Component({\n  selector: 'demo-datetime-locales',\n  imports: [PctDatetime],\n  styles: ':host { display: grid; gap: 12px; max-inline-size: 24rem; }',\n  template: `\n    <pct-datetime label=\"American English\" locale=\"en-US\" [(value)]=\"value\" />\n    <pct-datetime label=\"Polish\" locale=\"pl-PL\" [(value)]=\"value\" />\n    <pct-datetime label=\"Vietnamese\" locale=\"vi-VN\" [(value)]=\"value\" />\n  `,\n})\nexport class DatetimeLocalesDemo {\n  readonly value = signal<PctDateTime | null>('2026-12-01T13:05');\n}"
        },
        {
          "key": "bounds",
          "title": "Within bounds, around taken slots",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctDateTime, PctDatetime } from '@pacit/components/datetime';\nimport { PctDay, pctWeekday } from '@pacit/components/date';\n\n/**\n * Within bounds, around taken slots\n *\n * `min` and `max` are a day and a time: the calendar gets the day of each, the columns the time\n * of a bound only on the bound's own day. `dateDisabled` refuses a day whole; `datetimeDisabled`\n * is asked of every time of the chosen day, and an hour every slot of which is taken is refused.\n */\n@Component({\n  selector: 'demo-datetime-bounds',\n  imports: [PctDatetime],\n  styles: ':host { display: grid; gap: 12px; max-inline-size: 24rem; }',\n  template: `\n    <pct-datetime\n      label=\"Office hours, weekdays\"\n      min=\"2026-12-01T09:00\"\n      max=\"2026-12-31T17:00\"\n      step=\"900\"\n      [dateDisabled]=\"weekend\"\n      [(value)]=\"office\"\n    />\n    <pct-datetime\n      label=\"Meeting room\"\n      [datetimeDisabled]=\"taken\"\n      [(value)]=\"room\"\n    />\n  `,\n})\nexport class DatetimeBoundsDemo {\n  readonly office = signal<PctDateTime | null>(null);\n  readonly room = signal<PctDateTime | null>('2026-12-02T09:00');\n\n  readonly weekend = (day: PctDay): boolean => pctWeekday(day) >= 6;\n\n  /** Booked on 2 December: the whole of ten o'clock, and half past twelve. */\n  readonly taken = (value: PctDateTime): boolean =>\n    value.startsWith('2026-12-02T10:') || value === '2026-12-02T12:30';\n}"
        }
      ],
      "api": [
        {
          "name": "PctDatetime",
          "kind": "component",
          "selector": "pct-datetime",
          "description": "Datetime field — two texts under one label, the day's and the time's, each formatted and parsed per locale, with ONE button and ONE panel holding a calendar and columns of hours and minutes. It is the ARIA APG's Date Picker Dialog whole, through PctCalendar as PctDate holds it, with the time's columns beside the grid — a role=\"listbox\" per field, claimed on their own card.",
          "members": [
            {
              "name": "value",
              "kind": "model",
              "required": false,
              "type": "PctDateTime | null",
              "default": "null",
              "description": "The chosen day and time, or null — which it is until both halves hold one.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "disabled",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Blocks the control and greys it — the native disabled on both texts, so they leave the tab order as well. With [formField] the directive writes it, as it writes every input of the FormUiControl contract.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "readonly",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Keeps the value: both texts take the native readonly and the button is disabled with them, while the field stays focusable.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "invalid",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "The form's verdict; shown only once touched, so an empty form does not open red.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "touched",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the user has left the field once; with invalid it gates the error face.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "required",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Marks the label with the required sign; with [formField] it follows the schema's required().",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "errors",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[]",
              "default": "[]",
              "description": "The form's validation errors; the first one's message takes the hint's place once the field is touched.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "warnings",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[] | undefined",
              "default": "undefined",
              "description": "A verdict without a veto (0087): shown after the error and before the hint once the field is touched, in the warning tone, with aria-invalid untouched. Left unbound, the control reads what pctWarn() wrote for its own [formField]; bound — [] included — the list given stands.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "name",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The native name — what a form submission calls the value. It names a hidden input that carries the value as the platform writes one, YYYY-MM-DDTHH:mm, and not either text, which are written in the user's language.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "min",
              "kind": "input",
              "required": false,
              "type": "unknown",
              "default": "undefined",
              "description": "The earliest day and time. It belongs to the FormUiControl contract, which spells an absent bound undefined, and a signal form gives it through pctDateTimeBounds — the compiler refuses the attribute beside [formField].",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "max",
              "kind": "input",
              "required": false,
              "type": "unknown",
              "default": "undefined",
              "description": "The latest day and time — as min, a bound on the panel's walk and not a rewrite of what was typed.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "step",
              "kind": "input",
              "required": false,
              "type": "number",
              "default": "60",
              "description": "The step in seconds — the time's, 60 by default. It decides the columns' rows, whether a seconds column exists and whether the value carries :ss, and nothing about typed text. It counts from midnight on every day, not from min: a bound off the step is moved inward onto it before the columns see it (0090 §5). A step the columns cannot list is refused with a dev-mode warning and read as 60.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "dateDisabled",
              "kind": "input",
              "required": false,
              "type": "PctDayPredicate | null",
              "default": "null",
              "description": "Days inside the bounds that cannot be picked — handed to the calendar as it is, so a day it refuses is refused whole, hours included.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "datetimeDisabled",
              "kind": "input",
              "required": false,
              "type": "PctDateTimePredicate | null",
              "default": "null",
              "description": "Slots inside the bounds that cannot be taken — a meeting room's bookings. The field closes it over the chosen day and asks it of every time on the step, in the columns' rows: an hour is refused when every time in it is. It never reaches the calendar; a day whose every slot it refuses is still a day the grid offers, and a consumer who wants that day greyed says so through dateDisabled. A typed value it refuses is kept, and the form's to refuse.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "touch",
              "kind": "output",
              "required": false,
              "type": "void",
              "default": null,
              "description": "Emitted when focus leaves the whole field — both texts, the button and the panel — and not when it moves between them; lets the form mark the field as touched.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "label",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The visible label, rendered by the control itself when it stands outside a pct-field; inside one, the field's label is the name. Each text is named by it and a word of its own.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "hint",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "A line of help under the control, outside a pct-field; the first error message takes its place while the field is invalid and touched.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name of a field with no visible label — an INPUT rather than an aria-label on the tag, because the texts sit inside this template and an ARIA name on the roleless host is ignored. Each text is named by it and a word of its own.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "ariaLabelledby",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "As ariaLabel, for a name that already stands somewhere on the page.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "locale",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "Overrides the application's LOCALE_ID for this field — the order of the two texts, the day's order and the clock all follow it.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "firstDayOfWeek",
              "kind": "input",
              "required": false,
              "type": "number",
              "default": "0",
              "description": "Which day the calendar's week starts on, 1 (Monday) … 7 (Sunday). Absent, the locale decides.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "showFormat",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "true",
              "description": "Whether the two format hints stand in the texts while they are empty (dd.mm.yyyy and hh:mm).",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "size",
              "kind": "input",
              "required": false,
              "type": "PctSize",
              "default": "config.defaultSize",
              "description": "Height 28 / 36 / 44 px — the axis every field shares; from providePctConfig by default (req-api-config).",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "panelAlign",
              "kind": "input",
              "required": false,
              "type": "PctDatetimePanelAlign",
              "default": "'start'",
              "description": "Which edge of the field the panel lines up with.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-datetime",
              "note": ""
            },
            {
              "attribute": "data-pct-size",
              "kind": "attribute",
              "bound": "size()",
              "note": ""
            },
            {
              "attribute": "data-pct-open",
              "kind": "attribute",
              "bound": "open() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-invalid",
              "kind": "attribute",
              "bound": "showInvalid() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-warning",
              "kind": "attribute",
              "bound": "showWarning() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-disabled",
              "kind": "attribute",
              "bound": "disabled() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-malformed",
              "kind": "attribute",
              "bound": "malformed() ? \"\" : null",
              "note": "What the control knows and the form cannot: there is text in a half and it is not one."
            },
            {
              "attribute": "data-pct-in-field",
              "kind": "attribute",
              "bound": "inField ? \"\" : null",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctDateTime",
          "kind": "type",
          "selector": null,
          "detail": "string",
          "description": "A day and a time of it, written the way the platform already writes one: YYYY-MM-DDTHH:mm, or YYYY-MM-DDTHH:mm:ss where the field's step has seconds in it — a PctDay, a T and a PctTimeOfDay.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctDateTimeParts",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The two halves of a datetime — what the field's two texts hold.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "isPctDateTime",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Whether a string is a datetime this library will work with: a PctDay, one capital T, and a PctTimeOfDay — each half held to its own guard, so the domain is theirs: the day runs from 0000-01-01 to 275760-09-13 and the time refuses 24:00, a leap second and a fraction.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctDateTime",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Joins a day and a time into one value, in the time's shape: 13:05 makes …T13:05, and 13:05:00 makes …T13:05:00.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctDateTimeParts",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The two halves of a datetime, each in the shape it was written in. The caller has already established that it is one.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctDateTimeAsUtc",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The pair as the one Date anything outside this file is allowed to see: those fields in UTC — which is what Intl.DateTimeFormat with timeZone: 'UTC' reads back as the same day and time, exactly, in all three engines (0090, B9). It is the day's pctDayAsUtc with an hour on it, and it is not an instant anybody meant: no zone the event happens in is asked.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctCompareDateTimes",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "-1, 0 or 1 — the days first, then the times. …T13:05 and …T13:05:00, one value in two shapes, compare equal; and the day's compare reads a year of five digits as later than one of four, where a string compare would not.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctClampDateTime",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Held inside the bounds, either of which may be absent — undefined, as the FormUiControl contract spells an absent bound. A clamp invents no value: it hands back the one it was given or a bound, as written, and a value equal to a bound in the other shape is inside.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctAddDateTimeMinutes",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "n minutes on (or back), and the day carries: …01T23:30 plus 45 minutes is …02T00:15. That is PlainDateTime's answer and the platform's own — a datetime input's stepUp() at 23:59 is the next day's 00:00 in all three engines, where the same call on a time input stops or comes round (0090, A10 and B7). The time's shape is kept: a whole minute never moves the seconds.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctNowDateTime",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The day and the time on the LOCAL clock — the one read of local time in this file, and the only one that belongs: \"what day and what time is it\" is a question about where the user is standing, and every other question here is arithmetic on a value that already has an answer.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctDateTimePredicate",
          "kind": "type",
          "selector": null,
          "detail": "(value: PctDateTime) => boolean",
          "description": "A predicate on a datetime: true refuses it — a slot already taken on a day.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctDatetimePanelAlign",
          "kind": "type",
          "selector": null,
          "detail": "'start' | 'end'",
          "description": "Which edge of the field the panel lines its own up with.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctDatetime",
          "kind": "component",
          "selector": "pct-datetime",
          "detail": null,
          "description": "Datetime field — two texts under one label, the day's and the time's, each formatted and parsed per locale, with ONE button and ONE panel holding a calendar and columns of hours and minutes. It is the ARIA APG's Date Picker Dialog whole, through PctCalendar as PctDate holds it, with the time's columns beside the grid — a role=\"listbox\" per field, claimed on their own card.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctDateTimeBound",
          "kind": "type",
          "selector": null,
          "detail": "PctDateTime | undefined | LogicFn<TValue, PctDateTime | undefined, TPathKind>",
          "description": "A bound pctDateTimeBounds takes: a datetime, or a function of the field's context that returns one — the two shapes Angular's own min() takes. One that is not a datetime, undefined among them, is no bound, as it is on the field's input.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctDateTimeBoundError",
          "kind": "type",
          "selector": null,
          "detail": "| { readonly kind: 'minDateTime'",
          "description": "What pctDateTimeBounds reports: minDateTime for a datetime before min, maxDateTime for one after max — and both at once for one between the two of a reversed pair, the native element's reading on a datetime (0090, A8). Each carries the bound it broke, as the bound was written.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctDateTimeBoundsConfig",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The bounds of pctDateTimeBounds, and the sentence its errors carry — one for both, or one written from the error, which holds the bound.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctDateTimeBounds",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Bounds a datetime field in a signal form: what Angular's min() / max() do for a number and minDate() / maxDate() for a Date, for a PctDateTime — which none of them takes (their paths are a number's and a Date's, and the latter read the value with getTime()), and which the compiler refuses as an attribute beside [formField] (NG8022). The bounds reach the field's min / max, so the calendar and the columns walk inside them, and the form grades the value: a datetime outside is minDateTime or maxDateTime.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        }
      ],
      "parts": [
        {
          "name": "date-control",
          "description": "the day's text"
        },
        {
          "name": "error",
          "description": "the message when the value is invalid, or a half is not one, or one is missing"
        },
        {
          "name": "hint",
          "description": "the hint under the field"
        },
        {
          "name": "label",
          "description": "the label of the field, pointing at the text that stands first"
        },
        {
          "name": "panel",
          "description": "the dialog the calendar and the columns stand in"
        },
        {
          "name": "time-control",
          "description": "the time's text"
        },
        {
          "name": "toggle",
          "description": "the button that opens the panel"
        },
        {
          "name": "warning",
          "description": "the message when the value is allowed and suspect — after the error, before the hint (0087)"
        }
      ],
      "tokens": [
        {
          "name": "--pct-datetime-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the datetime field."
        },
        {
          "name": "--pct-datetime-bg-disabled",
          "type": "color",
          "ref": "{pct.surface-disabled}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the datetime field while disabled."
        },
        {
          "name": "--pct-datetime-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the datetime field."
        },
        {
          "name": "--pct-datetime-border-disabled",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge colour of the datetime field while disabled."
        },
        {
          "name": "--pct-datetime-border-focus",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge colour of the datetime field when focused."
        },
        {
          "name": "--pct-datetime-border-hover",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Edge colour of the datetime field on hover."
        },
        {
          "name": "--pct-datetime-border-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Edge colour of the datetime field when invalid."
        },
        {
          "name": "--pct-datetime-border-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Edge colour of the datetime field under a warning (0087)."
        },
        {
          "name": "--pct-datetime-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the datetime field."
        },
        {
          "name": "--pct-datetime-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of the datetime field while disabled."
        },
        {
          "name": "--pct-datetime-fg-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Text colour of the datetime field when invalid."
        },
        {
          "name": "--pct-datetime-fg-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Text colour of the warning line (0087)."
        },
        {
          "name": "--pct-datetime-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of the datetime field."
        },
        {
          "name": "--pct-datetime-font-size-lg",
          "type": "dimension",
          "ref": "{pct.font.size.lg}",
          "light": "16px",
          "dark": "16px",
          "description": "Font size of the datetime field at size lg."
        },
        {
          "name": "--pct-datetime-font-size-sm",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of the datetime field at size sm."
        },
        {
          "name": "--pct-datetime-hint-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the hint."
        },
        {
          "name": "--pct-datetime-icon-size",
          "type": "dimension",
          "ref": null,
          "light": "16px",
          "dark": "16px",
          "description": "Size of the icon."
        },
        {
          "name": "--pct-datetime-label-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the label."
        },
        {
          "name": "--pct-datetime-padding-x",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of the datetime field."
        },
        {
          "name": "--pct-datetime-padding-x-lg",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of the datetime field at size lg."
        },
        {
          "name": "--pct-datetime-padding-x-sm",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of the datetime field at size sm."
        },
        {
          "name": "--pct-datetime-padding-y",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Vertical padding of the datetime field."
        },
        {
          "name": "--pct-datetime-padding-y-lg",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Vertical padding of the datetime field at size lg."
        },
        {
          "name": "--pct-datetime-padding-y-sm",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Vertical padding of the datetime field at size sm."
        },
        {
          "name": "--pct-datetime-panel-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the panel."
        },
        {
          "name": "--pct-datetime-panel-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the panel."
        },
        {
          "name": "--pct-datetime-panel-gap",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Gap between the calendar and the columns in the panel."
        },
        {
          "name": "--pct-datetime-panel-padding-x",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of the panel."
        },
        {
          "name": "--pct-datetime-panel-padding-y",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Vertical padding of the panel."
        },
        {
          "name": "--pct-datetime-panel-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the panel."
        },
        {
          "name": "--pct-datetime-panel-shadow",
          "type": "shadow",
          "ref": "{pct.shadow-panel}",
          "light": "0 8px 24px rgba(15, 23, 42, 0.14)",
          "dark": "0 8px 24px rgba(0, 0, 0, 0.55)",
          "description": "Shadow of the panel."
        },
        {
          "name": "--pct-datetime-placeholder-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the placeholder."
        },
        {
          "name": "--pct-datetime-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the datetime field."
        },
        {
          "name": "--pct-datetime-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Touch-target floor of the datetime field."
        },
        {
          "name": "--pct-datetime-toggle-bg-hover",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the button that opens the panel on hover."
        },
        {
          "name": "--pct-datetime-toggle-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the button that opens the panel."
        },
        {
          "name": "--pct-datetime-toggle-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the button that opens the panel."
        },
        {
          "name": "--pct-datetime-toggle-size",
          "type": "dimension",
          "ref": null,
          "light": "20px",
          "dark": "20px",
          "description": "Size of the button that opens the panel."
        }
      ],
      "texts": [
        "dateDayLetter",
        "dateMalformed",
        "dateMonthLetter",
        "dateYearLetter",
        "datetimeDay",
        "datetimeIncomplete",
        "datetimeOpen",
        "datetimeTime",
        "fieldWarning",
        "timeHourLetter",
        "timeMalformed",
        "timeMinuteLetter",
        "timeSecondLetter"
      ],
      "keyboard": "| key                        | effect                                                                                | test                                                                                                                                |\n| -------------------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |\n| any text                   | typed into a half, parsed when that half is left                                      | `apps/sandbox-e2e/src/datetime.spec.ts › junk in one half keeps the other`                                                          |\n| `Tab`                      | the first text → the second → the button → out; leaving the whole is `touch`          | `apps/sandbox-e2e/src/datetime.spec.ts › a day with no time is no value`                                                            |\n| `Enter` on the button      | opens the panel, focus on the grid's cell — the held day, or today                    | `apps/sandbox-e2e/src/datetime.spec.ts › opens on the day and the time the field holds`                                             |\n| arrows in the grid         | walk the days, as on [`PctCalendar`](./calendar.md)                                   | `apps/sandbox-e2e/src/datetime.spec.ts › a day picked moves on to the hours`                                                        |\n| `Enter` in the grid        | writes the day's text, leaves the time's as typed, and hands focus to the hour column | `apps/sandbox-e2e/src/datetime.spec.ts › a day picked moves on to the hours`, `libs/components/datetime/src/datetime-field.spec.ts` |\n| `Tab` in the panel         | the month buttons, the grid's cell, then the columns one stop each — the grid first   | `apps/sandbox-e2e/src/datetime.spec.ts › Tab walks the grid, then the columns`                                                      |\n| `Shift+Tab` from the hours | back to the grid's cell — the way to a day picked wrong                               | `apps/sandbox-e2e/src/datetime.spec.ts › Tab walks the grid, then the columns`                                                      |\n| arrows in a column         | write the time as they walk, as on [`PctTimeColumns`](./columns.md)                   | `apps/sandbox-e2e/src/datetime.spec.ts › a day picked moves on to the hours`                                                        |\n| `Enter` in a column        | takes the time and closes the panel, focus back on the first text                     | `apps/sandbox-e2e/src/datetime.spec.ts › a day picked moves on to the hours`                                                        |\n| `Escape` in the panel      | closes it with the value as the halves left it, focus back on the first text          | `apps/sandbox-e2e/src/datetime.spec.ts › Escape closes it`, `libs/components/datetime/src/datetime-field.spec.ts`                   |\n| `Tab` out of the panel     | closes it, focus **spliced back** onto the first text                                 | `apps/sandbox-e2e/src/datetime.spec.ts › Tab walks the grid, then the columns`                                                      |\n\nThe grid's and the columns' own maps are on their cards. The field adds no key of its own: what\na user types is text, and the arrows do not step the segment under the caret, because the\nengines step the native element's segments three ways (0090, A10) and a reader is never told a\ntextbox has a value to step.",
      "decisions": [
        {
          "id": "0090",
          "title": "A day with a time is a wall clock with a date, and the field that takes one is two texts under one label"
        },
        {
          "id": "0093",
          "title": "A bound of the field's own type reaches it through the platform's limit key, and the module ships the rule"
        },
        {
          "id": "0086",
          "title": "A time of day is a wall clock, and the field that takes one is text the application formats"
        },
        {
          "id": "0089",
          "title": "One value, more than one control, and the consumer chooses which"
        },
        {
          "id": "0043",
          "title": "A day is not an instant, and the field that takes one is text the application formats"
        },
        {
          "id": "0070",
          "title": "What the control knows and the form cannot is a second channel"
        },
        {
          "id": "0035",
          "title": "A filter is a question, not a value"
        },
        {
          "id": "0031",
          "title": "A panel's tab order belongs to its trigger"
        },
        {
          "id": "0025",
          "title": "A panel says whether it takes focus, and the rest of the focus layer waits for a consumer"
        },
        {
          "id": "0024",
          "title": "The overlay layer carries what an overlay severs, and borrows the closing stack"
        },
        {
          "id": "0022",
          "title": "One message line, and it is the error that takes it"
        },
        {
          "id": "0005",
          "title": "Signal forms without ControlValueAccessor"
        },
        {
          "id": "0003",
          "title": "Wrapper and control"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 85.64,
          "killed": 692,
          "survived": 112,
          "mutants": 808,
          "files": 1
        },
        "e2e": {
          "cases": 15,
          "spec": "apps/sandbox-e2e/src/datetime.spec.ts"
        },
        "pairs": 18,
        "baselines": 3,
        "cost": {
          "elements": 13,
          "depth": 6,
          "listeners": 8,
          "renders": 1,
          "micros": 8586,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "select",
      "classes": [
        "PctSelect",
        "PctMultiSelect"
      ],
      "role": "choice list with a panel of its own",
      "category": "Choices",
      "status": "released",
      "entrypoint": "@pacit/components/select",
      "selectors": [
        "pct-select",
        "pct-multi-select"
      ],
      "harnesses": [
        "PctSelectHarness",
        "PctMultiSelectHarness"
      ],
      "docs": "/components/select",
      "summary": "A choice list with a panel of its own, holding one answer or many.",
      "usage": {
        "code": "<pct-select ariaLabel=\"Country\" [options]=\"countries\" [(value)]=\"country\" />",
        "lang": "html"
      },
      "examples": [
        {
          "key": "groups",
          "title": "Groups, and a filter",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctSelect, PctSelectItem } from '@pacit/components/select';\n\n/**\n * Groups, and a filter\n *\n * An item can be a group — a label with options under it — and `filterable` turns the\n * trigger into an input that narrows the list as you type, the filter matching labels.\n */\n@Component({\n  selector: 'demo-select-groups',\n  imports: [PctSelect],\n  styles: ':host { display: grid; gap: 12px; max-inline-size: 24rem; }',\n  template: `\n    <pct-select\n      label=\"Region\"\n      [options]=\"regions\"\n      [(value)]=\"region\"\n      filterable\n    />\n    <p>Deploying to: {{ region() ?? 'nowhere yet' }}</p>\n  `,\n})\nexport class SelectGroupsDemo {\n  readonly region = signal<string | null>(null);\n  readonly regions: readonly PctSelectItem[] = [\n    {\n      label: 'Europe',\n      options: [\n        { value: 'eu-west', label: 'West' },\n        { value: 'eu-central', label: 'Central' },\n      ],\n    },\n    {\n      label: 'Americas',\n      options: [\n        { value: 'us-east', label: 'US East' },\n        { value: 'sa-east', label: 'South America' },\n      ],\n    },\n    {\n      label: 'Asia Pacific',\n      options: [\n        { value: 'ap-south', label: 'South' },\n        { value: 'ap-ne', label: 'North East', disabled: true },\n      ],\n    },\n  ];\n}"
        },
        {
          "key": "multi",
          "title": "Many answers",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctMultiSelect, PctSelectOption } from '@pacit/components/select';\n\n/**\n * Many answers\n *\n * `pct-multi-select` holds an array: each pick toggles a value, the trigger names how many\n * are chosen, and the same keyboard walks the same panel.\n */\n@Component({\n  selector: 'demo-select-multi',\n  imports: [PctMultiSelect],\n  styles: ':host { display: grid; gap: 12px; max-inline-size: 24rem; }',\n  template: `\n    <pct-multi-select label=\"Notify\" [options]=\"channels\" [(value)]=\"chosen\" />\n    <p>Via: {{ chosen().length ? chosen().join(', ') : 'nothing yet' }}</p>\n  `,\n})\nexport class SelectMultiDemo {\n  readonly chosen = signal<string[]>(['email']);\n  readonly channels: readonly PctSelectOption[] = [\n    { value: 'email', label: 'E-mail' },\n    { value: 'sms', label: 'Text message' },\n    { value: 'push', label: 'Push' },\n    { value: 'slack', label: 'Slack' },\n  ];\n}"
        }
      ],
      "api": [
        {
          "name": "PctSelect",
          "kind": "component",
          "selector": "pct-select",
          "description": "A single-choice select with a panel of its own (not a native <select>).",
          "members": [
            {
              "name": "disabled",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Blocks the control and greys it — the native disabled, so it leaves the tab order as well. With [formField] the directive writes it, as it writes every input of the FormUiControl contract.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "readonly",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Holds the value and keeps the panel closed while the control stays focusable; aria-readonly says so.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "invalid",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "The form's verdict; shown only once touched, so an empty form does not open red.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "touched",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the user has left the field once; with invalid it gates the error face.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "required",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Marks the label with the required sign; with [formField] it follows the schema's required().",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "errors",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[]",
              "default": "[]",
              "description": "The form's validation errors; the first one's message takes the hint's place once the field is touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "warnings",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[] | undefined",
              "default": "undefined",
              "description": "A verdict without a veto (0087): shown after the error and before the hint once the field is touched, in the warning tone, with aria-invalid untouched. Left unbound, the control reads what pctWarn() wrote for its own [formField]; bound — [] included — the list given stands.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "name",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The native name — what a form submission calls the value.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "touch",
              "kind": "output",
              "required": false,
              "type": "void",
              "default": null,
              "description": "Emitted on blur — lets the form mark the field as touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "options",
              "kind": "input",
              "required": false,
              "type": "readonly PctSelectItem<T>[]",
              "default": "[]",
              "description": "The option list. The values have to be unique by compareWith: a value is what maps back to an option (the first match wins), so of two options sharing one value only the earlier is ever reachable — picking the later one shows the earlier one's label and leaves aria-selected on it. Which option an equal value denotes is not the component's to decide, so it does not repair the list quietly: in dev mode it says so.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "label",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The visible label, rendered by the control itself when it stands outside a pct-field; inside one, the field's label is the name.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "hint",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "A line of help under the control, outside a pct-field; the first error message takes its place while the field is invalid and touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name of a control with no visible label — an INPUT rather than an aria-label written on the tag, because the tag cannot carry one: role=\"combobox\" sits on the trigger inside, the host has no role at all, and an ARIA name on a roleless element is ignored (aria-label is prohibited for the generic role). Without this a standalone select is an unnamed combobox and the consumer has no way in (req-a11y-built-in).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabelledby",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "As ariaLabel, for a name that already stands somewhere on the page — a heading, the header of the column the control sits in. It wins over ariaLabel and over label, in ARIA's order and not ours.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "placeholder",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": null,
              "description": "The placeholder. With no value it comes from PCT_TEXTS — and it comes at render time, not at construction: an input's default value is produced once, so an application switching language at runtime would be left with the string from before the change (0014). placeholder=\"\" stays an empty placeholder rather than a return to the default — absent and empty mean different things.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "size",
              "kind": "input",
              "required": false,
              "type": "PctSize",
              "default": "config.defaultSize",
              "description": "Height 28 / 36 / 44 px — the axis every field shares; from providePctConfig by default (req-api-config).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "compareWith",
              "kind": "input",
              "required": false,
              "type": "PctCompareWith<T>",
              "default": "pctSameValue",
              "description": "Value equality. Identity by default, which for strings and numbers is the same as ===. Entities need comparison by key — an instance from the server is not the same reference as an option on the list, so without this the selected item would not highlight after the form loads.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "filterable",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the trigger is a text field and the list narrows to what is typed into it.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "filterText",
              "kind": "model",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "What has been typed into the trigger. A model, so an application filtering on a server can read the question and answer it with another options list — and the control still owns the clearing, because the question belongs to the panel: it is set to '' when the panel closes and when a pick answers it. It is not the value and never becomes one.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "filterWith",
              "kind": "input",
              "required": false,
              "type": "PctSelectFilter<T>",
              "default": "pctFilterByLabel",
              "description": "What counts as a match. The default folds case on the label and asks for includes; anything more than that — a second field, a code beside the label, an idea of which letters are the same letter — is the application's, because it is the application that knows the language (pctFilterByLabel, and lesson-101 for why).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "clearable",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the control draws a cross that takes back what the trigger is showing — the answer, or the question being typed while the panel is up (0036).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "loading",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "The list is on its way. A fact about the list, not about the control (0037): it takes nothing away — not the focus, not the answer already given, not the rows still on the screen — and says two things instead. The panel carries aria-busy, so a reader knows that what it is reading may not be the last word, and an empty one says \"loading\" where it would otherwise have said \"no options\": that sentence is a conclusion, and a list still coming has reached none.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "virtual",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Draw only the rows the panel can show. It is a promise about how many rows exist, and that is why it is the control's to make: the panel builds its own rows, so it is the one thing here that can decide not to (0033).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "panelWidth",
              "kind": "input",
              "required": false,
              "type": "PctSelectPanelWidth",
              "default": "'field'",
              "description": "Width of the dropdown panel — equal to the control by default ('field'). The panel then comes out exactly from its edge, so the list reads as an extension of the field. 'auto' fits the width to the longest option (without narrowing the panel below the control), and a CSS length sets it outright.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "panelAlign",
              "kind": "input",
              "required": false,
              "type": "PctSelectPanelAlign",
              "default": "'start'",
              "description": "Alignment of the panel to the control when it is wider or narrower than it.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "value",
              "kind": "model",
              "required": false,
              "type": "NoInfer<T> | null",
              "default": "null",
              "description": "The selected value — a required field of the FormValueControl contract. The type is T | null, because \"nothing selected\" is a state reachable for every T: the select starts empty and the cross returns to it.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "emptyValue",
              "kind": "input",
              "required": false,
              "type": "NoInfer<T> | null",
              "default": "null",
              "description": "The value standing for no choice — what the cross (clearable) writes. null by default, but an application with a non-nullable field (plan: string) supplies its own (emptyValue=\"\"), so that a clear does not write null into the model against its type. A form's reset writes nothing here: the value it resets to is the form's (0092).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-select",
              "note": ""
            },
            {
              "attribute": "data-pct-size",
              "kind": "attribute",
              "bound": "size()",
              "note": ""
            },
            {
              "attribute": "data-pct-open",
              "kind": "attribute",
              "bound": "open() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-invalid",
              "kind": "attribute",
              "bound": "showInvalid() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-warning",
              "kind": "attribute",
              "bound": "warningLit() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-disabled",
              "kind": "attribute",
              "bound": "disabled() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-multiple",
              "kind": "attribute",
              "bound": "multiple ? \"\" : null",
              "note": "The one state a consumer's stylesheet cannot derive from the parts: both controls draw the same DOM, so the tag is what tells them apart, and a tag is not a selector the parts contract exposes (<code>req-api-attributes</code>)."
            },
            {
              "attribute": "data-pct-clearable",
              "kind": "attribute",
              "bound": "clearable() ? \"\" : null",
              "note": "Not &quot;there is a cross standing&quot; but &quot;this control has one&quot;: the space it takes is reserved by the input alone, so a trigger's text does not reflow the moment an answer appears under it."
            },
            {
              "attribute": "data-pct-in-field",
              "kind": "attribute",
              "bound": "inField ? \"\" : null",
              "note": "Inside the chrome <code>pct-field</code> draws the border and the label — the control hands them over."
            }
          ]
        },
        {
          "name": "PctMultiSelect",
          "kind": "component",
          "selector": "pct-multi-select",
          "description": "A many-choice select — the same panel, the same walk and the same file as pct-select, with a value that is a list.",
          "members": [
            {
              "name": "disabled",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Blocks the control and greys it — the native disabled, so it leaves the tab order as well. With [formField] the directive writes it, as it writes every input of the FormUiControl contract.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "readonly",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Holds the value and keeps the panel closed while the control stays focusable; aria-readonly says so.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "invalid",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "The form's verdict; shown only once touched, so an empty form does not open red.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "touched",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the user has left the field once; with invalid it gates the error face.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "required",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Marks the label with the required sign; with [formField] it follows the schema's required().",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "errors",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[]",
              "default": "[]",
              "description": "The form's validation errors; the first one's message takes the hint's place once the field is touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "warnings",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[] | undefined",
              "default": "undefined",
              "description": "A verdict without a veto (0087): shown after the error and before the hint once the field is touched, in the warning tone, with aria-invalid untouched. Left unbound, the control reads what pctWarn() wrote for its own [formField]; bound — [] included — the list given stands.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "name",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The native name — what a form submission calls the value.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "touch",
              "kind": "output",
              "required": false,
              "type": "void",
              "default": null,
              "description": "Emitted on blur — lets the form mark the field as touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "options",
              "kind": "input",
              "required": false,
              "type": "readonly PctSelectItem<T>[]",
              "default": "[]",
              "description": "The option list. The values have to be unique by compareWith: a value is what maps back to an option (the first match wins), so of two options sharing one value only the earlier is ever reachable — picking the later one shows the earlier one's label and leaves aria-selected on it. Which option an equal value denotes is not the component's to decide, so it does not repair the list quietly: in dev mode it says so.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "label",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The visible label, rendered by the control itself when it stands outside a pct-field; inside one, the field's label is the name.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "hint",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "A line of help under the control, outside a pct-field; the first error message takes its place while the field is invalid and touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name of a control with no visible label — an INPUT rather than an aria-label written on the tag, because the tag cannot carry one: role=\"combobox\" sits on the trigger inside, the host has no role at all, and an ARIA name on a roleless element is ignored (aria-label is prohibited for the generic role). Without this a standalone select is an unnamed combobox and the consumer has no way in (req-a11y-built-in).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabelledby",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "As ariaLabel, for a name that already stands somewhere on the page — a heading, the header of the column the control sits in. It wins over ariaLabel and over label, in ARIA's order and not ours.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "placeholder",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": null,
              "description": "The placeholder. With no value it comes from PCT_TEXTS — and it comes at render time, not at construction: an input's default value is produced once, so an application switching language at runtime would be left with the string from before the change (0014). placeholder=\"\" stays an empty placeholder rather than a return to the default — absent and empty mean different things.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "size",
              "kind": "input",
              "required": false,
              "type": "PctSize",
              "default": "config.defaultSize",
              "description": "Height 28 / 36 / 44 px — the axis every field shares; from providePctConfig by default (req-api-config).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "compareWith",
              "kind": "input",
              "required": false,
              "type": "PctCompareWith<T>",
              "default": "pctSameValue",
              "description": "Value equality. Identity by default, which for strings and numbers is the same as ===. Entities need comparison by key — an instance from the server is not the same reference as an option on the list, so without this the selected item would not highlight after the form loads.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "filterable",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the trigger is a text field and the list narrows to what is typed into it.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "filterText",
              "kind": "model",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "What has been typed into the trigger. A model, so an application filtering on a server can read the question and answer it with another options list — and the control still owns the clearing, because the question belongs to the panel: it is set to '' when the panel closes and when a pick answers it. It is not the value and never becomes one.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "filterWith",
              "kind": "input",
              "required": false,
              "type": "PctSelectFilter<T>",
              "default": "pctFilterByLabel",
              "description": "What counts as a match. The default folds case on the label and asks for includes; anything more than that — a second field, a code beside the label, an idea of which letters are the same letter — is the application's, because it is the application that knows the language (pctFilterByLabel, and lesson-101 for why).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "clearable",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the control draws a cross that takes back what the trigger is showing — the answer, or the question being typed while the panel is up (0036).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "loading",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "The list is on its way. A fact about the list, not about the control (0037): it takes nothing away — not the focus, not the answer already given, not the rows still on the screen — and says two things instead. The panel carries aria-busy, so a reader knows that what it is reading may not be the last word, and an empty one says \"loading\" where it would otherwise have said \"no options\": that sentence is a conclusion, and a list still coming has reached none.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "virtual",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Draw only the rows the panel can show. It is a promise about how many rows exist, and that is why it is the control's to make: the panel builds its own rows, so it is the one thing here that can decide not to (0033).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "panelWidth",
              "kind": "input",
              "required": false,
              "type": "PctSelectPanelWidth",
              "default": "'field'",
              "description": "Width of the dropdown panel — equal to the control by default ('field'). The panel then comes out exactly from its edge, so the list reads as an extension of the field. 'auto' fits the width to the longest option (without narrowing the panel below the control), and a CSS length sets it outright.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "panelAlign",
              "kind": "input",
              "required": false,
              "type": "PctSelectPanelAlign",
              "default": "'start'",
              "description": "Alignment of the panel to the control when it is wider or narrower than it.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "value",
              "kind": "model",
              "required": false,
              "type": "NoInfer<T>[]",
              "default": "[]",
              "description": "The chosen values — the FormValueControl contract's one field, here a list. Empty is the empty list, so there is no emptyValue to declare: [] is reachable for every T and means the same thing in every application, which null did not.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-select",
              "note": ""
            },
            {
              "attribute": "data-pct-size",
              "kind": "attribute",
              "bound": "size()",
              "note": ""
            },
            {
              "attribute": "data-pct-open",
              "kind": "attribute",
              "bound": "open() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-invalid",
              "kind": "attribute",
              "bound": "showInvalid() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-warning",
              "kind": "attribute",
              "bound": "warningLit() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-disabled",
              "kind": "attribute",
              "bound": "disabled() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-multiple",
              "kind": "attribute",
              "bound": "multiple ? \"\" : null",
              "note": "The one state a consumer's stylesheet cannot derive from the parts: both controls draw the same DOM, so the tag is what tells them apart, and a tag is not a selector the parts contract exposes (<code>req-api-attributes</code>)."
            },
            {
              "attribute": "data-pct-clearable",
              "kind": "attribute",
              "bound": "clearable() ? \"\" : null",
              "note": "Not &quot;there is a cross standing&quot; but &quot;this control has one&quot;: the space it takes is reserved by the input alone, so a trigger's text does not reflow the moment an answer appears under it."
            },
            {
              "attribute": "data-pct-in-field",
              "kind": "attribute",
              "bound": "inField ? \"\" : null",
              "note": "Inside the chrome <code>pct-field</code> draws the border and the label — the control hands them over."
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctMultiSelect",
          "kind": "component",
          "selector": "pct-multi-select",
          "detail": null,
          "description": "A many-choice select — the same panel, the same walk and the same file as pct-select, with a value that is a list.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctSelect",
          "kind": "component",
          "selector": "pct-select",
          "detail": null,
          "description": "A single-choice select with a panel of its own (not a native <select>).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctSelectOptionContext",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What the select hands an option template on every row it draws.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctSelectOptionTemplate",
          "kind": "directive",
          "selector": "ng-template[pctSelectOption]",
          "detail": null,
          "description": "The template a consumer writes instead of the select's own option row (req-api-templates).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctSelectOption",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "An option of a select. The value is of any type T — business forms bind numeric ids, union members and whole entities, and narrowing it to a string forced every application into mapping by hand in both directions. T is a string by default, so string lists are written exactly as before.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctSelectPanelWidth",
          "kind": "type",
          "selector": null,
          "detail": "'field' | 'auto' | (string & {})",
          "description": "Width of the dropdown panel: - 'field' (the default) — exactly that of the visible control: the field border inside the chrome, the trigger alone outside it. The panel is then an extension of the field rather than a separate object, - 'auto' — as wide as the longest option, but never narrower than the control. For lists where an option's full text matters more than a flush edge, - a CSS length ('320px', '24rem') — the width outright.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctSelectPanelAlign",
          "kind": "type",
          "selector": null,
          "detail": "'start' | 'center' | 'end'",
          "description": "Alignment of the panel to the control when the panel does not have the control's width ('auto' or an explicit width). With panelWidth=\"field\" every variant gives the same result.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctSelectOptionGroup",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "A named section of a list — what a native <optgroup> is, and what the ARIA listbox pattern draws as a role=\"group\" with its label referenced by aria-labelledby.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctSelectItem",
          "kind": "type",
          "selector": null,
          "detail": "PctSelectOption<T> | PctSelectOptionGroup<T>",
          "description": "What options accepts: a flat list, a list of groups, or the two mixed — options standing before the first heading are exactly what a native <select> draws above its first <optgroup>.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctSelectFilter",
          "kind": "type",
          "selector": null,
          "detail": "( option: PctSelectOption<T>, query: string, ) => boolean",
          "description": "What decides whether an option stays in the panel while a filter is on: the option, and what the user has typed. A predicate rather than a mode name ('contains', 'startsWith') — the moment a list wants matching on a second field, on a code beside the label or on what a server said, a name in an enum has to grow another member and a function does not.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctFilterByLabel",
          "kind": "const",
          "selector": null,
          "detail": null,
          "description": "The default filter: the label, case-folded, contains what was typed.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctKeepAll",
          "kind": "const",
          "selector": null,
          "detail": null,
          "description": "The predicate that decides nothing: for a list somebody else has already narrowed. An application filtering on a server answers the question with another options list, and the control would otherwise narrow that answer a second time — a server that matched a city by its old name, by a code or by a misspelling would watch the row it found be taken out again by a client-side includes, in a library that never said it would (0037).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "arrow",
          "description": "the chevron at the end"
        },
        {
          "name": "clear",
          "description": "the button that empties the value"
        },
        {
          "name": "empty",
          "description": "the sentence shown when the list has nothing to show — beside the list, not in it (0069)"
        },
        {
          "name": "error",
          "description": "the message when invalid"
        },
        {
          "name": "group",
          "description": "a group of options"
        },
        {
          "name": "group-label",
          "description": "the heading of a group"
        },
        {
          "name": "hint",
          "description": "the hint under the control"
        },
        {
          "name": "label",
          "description": "the label of the control"
        },
        {
          "name": "list",
          "description": "the listbox inside the panel — the element that scrolls, and the one the trigger controls"
        },
        {
          "name": "option",
          "description": "one option"
        },
        {
          "name": "option-check",
          "description": "the mark on a chosen option"
        },
        {
          "name": "panel",
          "description": "the list box"
        },
        {
          "name": "placeholder",
          "description": "the text while nothing is chosen"
        },
        {
          "name": "trigger",
          "description": "the combobox element the panel opens from"
        },
        {
          "name": "value",
          "description": "the chosen value as text"
        },
        {
          "name": "warning",
          "description": "the message when the value is allowed and suspect — after the error, before the hint (0087)"
        }
      ],
      "tokens": [
        {
          "name": "--pct-select-arrow-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the arrow."
        },
        {
          "name": "--pct-select-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the select."
        },
        {
          "name": "--pct-select-bg-disabled",
          "type": "color",
          "ref": "{pct.surface-disabled}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the select while disabled."
        },
        {
          "name": "--pct-select-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the select."
        },
        {
          "name": "--pct-select-border-disabled",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge colour of the select while disabled."
        },
        {
          "name": "--pct-select-border-focus",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge colour of the select when focused."
        },
        {
          "name": "--pct-select-border-hover",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Edge colour of the select on hover."
        },
        {
          "name": "--pct-select-border-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Edge colour of the select when invalid."
        },
        {
          "name": "--pct-select-border-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Edge colour of the select under a warning (0087)."
        },
        {
          "name": "--pct-select-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the select."
        },
        {
          "name": "--pct-select-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of the select while disabled."
        },
        {
          "name": "--pct-select-fg-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Text colour of the select when invalid."
        },
        {
          "name": "--pct-select-fg-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Text colour of the warning line (0087)."
        },
        {
          "name": "--pct-select-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of the select."
        },
        {
          "name": "--pct-select-group-label-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the group label."
        },
        {
          "name": "--pct-select-group-label-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of the group label."
        },
        {
          "name": "--pct-select-group-label-font-weight",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of the group label."
        },
        {
          "name": "--pct-select-group-label-padding-x",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of the group label."
        },
        {
          "name": "--pct-select-group-label-padding-y",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Vertical padding of the group label."
        },
        {
          "name": "--pct-select-hint-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the hint."
        },
        {
          "name": "--pct-select-label-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the label."
        },
        {
          "name": "--pct-select-option-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of an option."
        },
        {
          "name": "--pct-select-option-bg-active",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the option the keyboard is on."
        },
        {
          "name": "--pct-select-option-bg-selected",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Background of an option when selected."
        },
        {
          "name": "--pct-select-option-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of an option."
        },
        {
          "name": "--pct-select-option-fg-active",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the option the keyboard is on."
        },
        {
          "name": "--pct-select-option-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of an option while disabled."
        },
        {
          "name": "--pct-select-option-fg-selected",
          "type": "color",
          "ref": "{pct.on-primary}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Text colour of an option when selected."
        },
        {
          "name": "--pct-select-option-padding-x",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of an option."
        },
        {
          "name": "--pct-select-option-padding-y",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Vertical padding of an option."
        },
        {
          "name": "--pct-select-padding-x",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of the select."
        },
        {
          "name": "--pct-select-padding-y",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Vertical padding of the select."
        },
        {
          "name": "--pct-select-panel-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the panel."
        },
        {
          "name": "--pct-select-panel-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the panel."
        },
        {
          "name": "--pct-select-panel-max-height",
          "type": "dimension",
          "ref": null,
          "light": "240px",
          "dark": "240px",
          "description": "Maximum height of the panel."
        },
        {
          "name": "--pct-select-panel-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the panel."
        },
        {
          "name": "--pct-select-panel-shadow",
          "type": "shadow",
          "ref": "{pct.shadow-panel}",
          "light": "0 8px 24px rgba(15, 23, 42, 0.14)",
          "dark": "0 8px 24px rgba(0, 0, 0, 0.55)",
          "description": "Shadow of the panel."
        },
        {
          "name": "--pct-select-placeholder-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the placeholder."
        },
        {
          "name": "--pct-select-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the select."
        }
      ],
      "texts": [
        "fieldWarning",
        "selectClear",
        "selectEmpty",
        "selectLoading",
        "selectNoMatches",
        "selectPlaceholder",
        "selectSeparator"
      ],
      "keyboard": "| key                          | effect                                      | test                                        |\n| ---------------------------- | ------------------------------------------- | ------------------------------------------- |\n| `↑` / `↓`                    | open the panel / change the active option   | `apps/sandbox-e2e/src/select.spec.ts`       |\n| `Home` / `End`               | first / last option                         | `apps/sandbox-e2e/src/select.spec.ts`       |\n| `Enter`                      | pick the active option, close               | `apps/sandbox-e2e/src/select.spec.ts`       |\n| `Enter` (many)               | toggle the active option, **stay open**     | `apps/sandbox-e2e/src/select.spec.ts`       |\n| `Escape`                     | close with no change                        | `apps/sandbox-e2e/src/select.spec.ts`       |\n| `Escape` (shut, `clearable`) | take the answer back — the keyboard's cross | `apps/sandbox-e2e/src/select.spec.ts`       |\n| characters                   | typeahead, skipping disabled options        | `libs/components/select/src/select.spec.ts` |\n\nWith `filterable` the map splits with the element the trigger is — the list keeps what it owns,\nthe caret takes back what a text field answers on its own (`req-api-platform`):\n\n| key                    | on a filtering trigger                                | test                                        |\n| ---------------------- | ----------------------------------------------------- | ------------------------------------------- |\n| `↑` / `↓`              | open the panel / change the active option             | `apps/sandbox-e2e/src/select.spec.ts`       |\n| characters             | narrow the list — and open the panel doing it         | `apps/sandbox-e2e/src/select.spec.ts`       |\n| `Home` / `End`         | **the caret's**: the ends of the text                 | `apps/sandbox-e2e/src/select.spec.ts`       |\n| space                  | **a character**, not a pick                           | `apps/sandbox-e2e/src/select.spec.ts`       |\n| `Enter`                | pick the active option (closed: the form's)           | `apps/sandbox-e2e/src/select.spec.ts`       |\n| `Escape` / `Tab`       | close, and the question goes with the panel           | `libs/components/select/src/select.spec.ts` |\n| a click on the trigger | opens, and **never** closes — a caret is not a switch | `libs/components/select/src/select.spec.ts` |\n\nThe map is unchanged by a custom option row — the keys sit on the trigger, not on the option\n(`apps/sandbox-e2e/src/select.spec.ts`, \"the keyboard is unchanged by a custom row\").",
      "decisions": [
        {
          "id": "0006",
          "title": "The anchor and inheritance in an overlay"
        },
        {
          "id": "0024",
          "title": "The overlay layer carries what an overlay severs, and borrows the closing stack"
        },
        {
          "id": "0010",
          "title": "A generic value and NoInfer"
        },
        {
          "id": "0003",
          "title": "Wrapper and control"
        },
        {
          "id": "0007",
          "title": "Configuration apart from texts"
        },
        {
          "id": "0025",
          "title": "A panel says whether it takes focus, and the rest of the focus layer waits for a consumer"
        },
        {
          "id": "0026",
          "title": "One channel per politeness, and a message with a home announces from it"
        },
        {
          "id": "0028",
          "title": "An icon set is a component, and an icon's box is the contract"
        },
        {
          "id": "0033",
          "title": "An option is a row of data, not a component the consumer projects"
        },
        {
          "id": "0034",
          "title": "Multiplicity is a type, so it is a tag"
        },
        {
          "id": "0035",
          "title": "A filter is a question, not a value"
        },
        {
          "id": "0036",
          "title": "A clear takes back what the trigger shows"
        },
        {
          "id": "0037",
          "title": "Loading is a fact about the list, and a cursor names an option"
        },
        {
          "id": "0038",
          "title": "A window is measured, and its spacer is not an element"
        },
        {
          "id": "0069",
          "title": "A message about the list is not an item in it"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 92.96,
          "killed": 66,
          "survived": 3,
          "mutants": 71,
          "files": 2
        },
        "e2e": {
          "cases": 60,
          "spec": "apps/sandbox-e2e/src/select.spec.ts"
        },
        "pairs": 21,
        "baselines": 13,
        "cost": {
          "elements": 8,
          "depth": 6,
          "listeners": 4,
          "renders": 1,
          "micros": 9900,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "checkbox",
      "classes": [
        "PctCheckbox"
      ],
      "role": "checkbox",
      "category": "Choices",
      "status": "released",
      "entrypoint": "@pacit/components/checkbox",
      "selectors": [
        "pct-checkbox"
      ],
      "harnesses": [
        "PctCheckboxHarness"
      ],
      "docs": "/components/checkbox",
      "summary": "A box the reader ticks, with the third state a parent of many options needs.",
      "usage": {
        "code": "<pct-checkbox label=\"Send me release notes\" [(checked)]=\"notes\" />",
        "lang": "html"
      },
      "examples": [
        {
          "key": "states",
          "title": "The states a form drives",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctCheckbox } from '@pacit/components/checkbox';\n\n/**\n * The states a form drives\n *\n * `required`, `invalid` and `touched` are the form's to set — the `FormField` directive keeps\n * them in sync — and here they are set by hand to show each face: the error appears once\n * the box is both invalid and touched, never before.\n */\n@Component({\n  selector: 'demo-checkbox-states',\n  imports: [PctCheckbox],\n  styles: ':host { display: grid; gap: 12px; }',\n  template: `\n    <pct-checkbox\n      label=\"I accept the terms\"\n      hint=\"Required to continue\"\n      [(checked)]=\"terms\"\n      required\n      [invalid]=\"!terms()\"\n      [touched]=\"true\"\n    />\n    <pct-checkbox label=\"Remember this device\" [checked]=\"true\" readonly />\n    <pct-checkbox label=\"Beta features\" hint=\"Not on this plan\" disabled />\n  `,\n})\nexport class CheckboxStatesDemo {\n  readonly terms = signal(false);\n}"
        }
      ],
      "api": [
        {
          "name": "PctCheckbox",
          "kind": "component",
          "selector": "pct-checkbox",
          "description": "Checkbox. A native signal-forms control — it implements FormCheckboxControl (req-api-signal-forms). That contract requires checked, and the component may not define a value property (reserved for FormValueControl).",
          "members": [
            {
              "name": "checked",
              "kind": "model",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Checked state — the only required field of the FormCheckboxControl contract.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "disabled",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Blocks the control and greys it — the native disabled, so it leaves the tab order as well. With [formField] the directive writes it, as it writes every input of the FormUiControl contract.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "readonly",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "A native checkbox has no readonly, so the click is swallowed here instead; the control stays focusable, unlike under disabled, and aria-readonly says so.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "invalid",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "The form's verdict; shown only once touched, so an empty form does not open red.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "touched",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the user has left the field once; with invalid it gates the error face.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "required",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Marks the label with the required sign; with [formField] it follows the schema's required().",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "errors",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[]",
              "default": "[]",
              "description": "The form's validation errors; the first one's message takes the hint's place once the field is touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "warnings",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[] | undefined",
              "default": "undefined",
              "description": "A verdict without a veto (0087): shown after the error and before the hint once the field is touched, in the warning tone, with aria-invalid untouched. Left unbound, the control reads what pctWarn() wrote for its own [formField]; bound — [] included — the list given stands.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "name",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The native name — what a form submission calls the value.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "touch",
              "kind": "output",
              "required": false,
              "type": "void",
              "default": null,
              "description": "Emitted on blur — lets the form mark the field as touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "label",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The visible label, rendered by the control itself when it stands outside a pct-field; inside one, the field's label is the name.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "hint",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "A line of help under the control, outside a pct-field; the first error message takes its place while the field is invalid and touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name of a checkbox with no visible label — an INPUT rather than an aria-label written on the tag, because the tag cannot carry one: the role sits on the <input> inside, the host has no role at all, and an ARIA name on a roleless element is ignored (aria-label is prohibited for the generic role) (req-a11y-built-in).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabelledby",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "As ariaLabel, for a name that already stands somewhere on the page — the header of the column a checkbox in a row belongs to. It wins over ariaLabel and over label.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "indeterminate",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Indeterminate state (a partial choice in a group, say). It is the native indeterminate PROPERTY and nothing else: the accessible tree reports it as mixed from the property alone, and an aria-checked written beside it would be ignored in both directions — measured in three engines and in Chromium's own tree, so none is written (0039, lesson-112).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-checkbox",
              "note": ""
            },
            {
              "attribute": "data-pct-checked",
              "kind": "attribute",
              "bound": "checked() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-indeterminate",
              "kind": "attribute",
              "bound": "indeterminate() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-invalid",
              "kind": "attribute",
              "bound": "showInvalid() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-warning",
              "kind": "attribute",
              "bound": "warningLit() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-disabled",
              "kind": "attribute",
              "bound": "disabled() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-in-field",
              "kind": "attribute",
              "bound": "inField ? \"\" : null",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctCheckbox",
          "kind": "component",
          "selector": "pct-checkbox",
          "detail": null,
          "description": "Checkbox. A native signal-forms control — it implements FormCheckboxControl (req-api-signal-forms). That contract requires checked, and the component may not define a value property (reserved for FormValueControl).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "box",
          "description": "the square drawn over it"
        },
        {
          "name": "control",
          "description": "the native input"
        },
        {
          "name": "error",
          "description": "the message when invalid"
        },
        {
          "name": "hint",
          "description": "the hint under the label"
        },
        {
          "name": "label",
          "description": "the label beside the box"
        },
        {
          "name": "mark",
          "description": "the check inside the box"
        },
        {
          "name": "warning",
          "description": "the message when the value is allowed and suspect — after the error, before the hint (0087)"
        }
      ],
      "tokens": [
        {
          "name": "--pct-checkbox-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the box."
        },
        {
          "name": "--pct-checkbox-bg-checked",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Background of the box when checked."
        },
        {
          "name": "--pct-checkbox-bg-disabled",
          "type": "color",
          "ref": "{pct.surface-disabled}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the box while disabled."
        },
        {
          "name": "--pct-checkbox-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the box."
        },
        {
          "name": "--pct-checkbox-border-checked",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge colour of the box when checked."
        },
        {
          "name": "--pct-checkbox-border-disabled",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge colour of the box while disabled."
        },
        {
          "name": "--pct-checkbox-border-hover",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Edge colour of the box on hover."
        },
        {
          "name": "--pct-checkbox-border-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Edge colour of the box when invalid."
        },
        {
          "name": "--pct-checkbox-fg-checked",
          "type": "color",
          "ref": "{pct.on-primary}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Colour of the check mark."
        },
        {
          "name": "--pct-checkbox-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Colour of the check mark while disabled."
        },
        {
          "name": "--pct-checkbox-fg-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Colour of the check mark when invalid."
        },
        {
          "name": "--pct-checkbox-fg-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Text colour of the warning line (0087); the mark keeps its colour, a warning being no fault of the control."
        },
        {
          "name": "--pct-checkbox-gap",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Gap between the box and its label."
        },
        {
          "name": "--pct-checkbox-hint-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the hint."
        },
        {
          "name": "--pct-checkbox-label-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the label."
        },
        {
          "name": "--pct-checkbox-radius",
          "type": "dimension",
          "ref": null,
          "light": "4px",
          "dark": "4px",
          "description": "Corner radius of the box."
        },
        {
          "name": "--pct-checkbox-size",
          "type": "dimension",
          "ref": null,
          "light": "18px",
          "dark": "18px",
          "description": "Size of the box."
        },
        {
          "name": "--pct-checkbox-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Touch-target floor of the box."
        }
      ],
      "texts": [
        "fieldWarning"
      ],
      "keyboard": "| key     | effect | test                             |\n| ------- | ------ | -------------------------------- |\n| `Space` | toggle | native `<input type=\"checkbox\">` |\n\nNo handling of its own ([`req-api-platform`](../requirements/api.md#req-api-platform)).\n`readonly` blocks the change **without losing focusability**.",
      "decisions": [
        {
          "id": "0005",
          "title": "Signal forms without ControlValueAccessor"
        },
        {
          "id": "0003",
          "title": "Wrapper and control"
        },
        {
          "id": "0028",
          "title": "An icon set is a component, and an icon's box is the contract"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 96.83,
          "killed": 61,
          "survived": 2,
          "mutants": 63,
          "files": 1
        },
        "e2e": {
          "cases": 5,
          "spec": "apps/sandbox-e2e/src/checkbox.spec.ts"
        },
        "pairs": 13,
        "baselines": 2,
        "cost": {
          "elements": 17,
          "depth": 6,
          "listeners": 7,
          "renders": 1,
          "micros": 8205,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "radio",
      "classes": [
        "PctRadioGroup",
        "PctRadio"
      ],
      "role": "group of mutually exclusive options",
      "category": "Choices",
      "status": "released",
      "entrypoint": "@pacit/components/radio",
      "selectors": [
        "pct-radio-group",
        "pct-radio"
      ],
      "harnesses": [
        "PctRadioGroupHarness",
        "PctRadioHarness"
      ],
      "docs": "/components/radio",
      "summary": "A group of mutually exclusive options, where the group is the form control and the options are its values.",
      "usage": {
        "code": "<pct-radio-group ariaLabel=\"Plan\" [(value)]=\"plan\">\n  <pct-radio value=\"free\">Free</pct-radio>\n  <pct-radio value=\"pro\">Pro</pct-radio>\n</pct-radio-group>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "horizontal",
          "title": "In a row, with a hint",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctRadio, PctRadioGroup } from '@pacit/components/radio';\n\n/**\n * In a row, with a hint\n *\n * `orientation=\"horizontal\"` lays the options on one line for a short list; `label` and\n * `hint` name the whole group, which is what a screen reader announces first.\n */\n@Component({\n  selector: 'demo-radio-horizontal',\n  imports: [PctRadio, PctRadioGroup],\n  styles: ':host { display: grid; gap: 12px; }',\n  template: `\n    <pct-radio-group\n      label=\"Billing\"\n      hint=\"Yearly saves two months\"\n      orientation=\"horizontal\"\n      [(value)]=\"cycle\"\n    >\n      <pct-radio value=\"monthly\">Monthly</pct-radio>\n      <pct-radio value=\"yearly\">Yearly</pct-radio>\n    </pct-radio-group>\n    <p>Billed {{ cycle() }}.</p>\n  `,\n})\nexport class RadioHorizontalDemo {\n  readonly cycle = signal<string | null>('yearly');\n}"
        }
      ],
      "api": [
        {
          "name": "PctRadioGroup",
          "kind": "component",
          "selector": "pct-radio-group",
          "description": "A single-choice group. The group is the form control (FormValueControl), not the individual options — from the form's point of view a radiogroup edits one value (req-api-signal-forms).",
          "members": [
            {
              "name": "value",
              "kind": "model",
              "required": false,
              "type": "T | null",
              "default": "null",
              "description": "The selected value — a required field of the FormValueControl contract. null means \"no option is selected\": the state the group is born in.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "disabled",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Disables every option at once — each native radio takes it, so the group leaves the tab order. With [formField] the directive writes it, as it writes every input of the FormUiControl contract.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "readonly",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Holds the choice while the options stay focusable; aria-readonly goes on the group, because the radio role has none.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "invalid",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "The form's verdict; shown only once touched, so an empty form does not open red.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "touched",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the user has left the field once; with invalid it gates the error face.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "required",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Marks the label with the required sign; with [formField] it follows the schema's required().",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "errors",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[]",
              "default": "[]",
              "description": "The form's validation errors; the first one's message takes the hint's place once the field is touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "warnings",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[] | undefined",
              "default": "undefined",
              "description": "A verdict without a veto (0087): shown after the error and before the hint once the field is touched, in the warning tone, with aria-invalid untouched. Left unbound, the control reads what pctWarn() wrote for its own [formField]; bound — [] included — the list given stands.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "name",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The shared name attribute of the native radios; generated by default.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "touch",
              "kind": "output",
              "required": false,
              "type": "void",
              "default": null,
              "description": "Emitted on blur — lets the form mark the field as touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "label",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The visible label, rendered by the control itself when it stands outside a pct-field; inside one, the field's label is the name.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "hint",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "A line of help under the control, outside a pct-field; the first error message takes its place while the field is invalid and touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "orientation",
              "kind": "input",
              "required": false,
              "type": "'vertical' | 'horizontal'",
              "default": "'vertical'",
              "description": "Which axis the options run along; written to aria-orientation and to the state attribute the stylesheet lays the group out by.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "compareWith",
              "kind": "input",
              "required": false,
              "type": "PctCompareWith<T>",
              "default": "pctSameValue",
              "description": "Value equality — as in pct-select; entities are compared by key.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "emptyValue",
              "kind": "input",
              "required": false,
              "type": "T | null",
              "default": "null",
              "description": "The \"no choice\" value. It was what a form's reset wrote into the model, and a reset writes nothing there any more, so nothing reads it (0092).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": "since 0.3.0 — reset the form with the value it should hold instead: form().reset({ plan: null }). Removed no earlier than 0.5.0."
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-radio-group",
              "note": ""
            },
            {
              "attribute": "role",
              "kind": "static",
              "bound": "radiogroup",
              "note": ""
            },
            {
              "attribute": "aria-labelledby",
              "kind": "attribute",
              "bound": "labelledBy()",
              "note": ""
            },
            {
              "attribute": "aria-describedby",
              "kind": "attribute",
              "bound": "describedBy()",
              "note": ""
            },
            {
              "attribute": "aria-invalid",
              "kind": "attribute",
              "bound": "showInvalid() ? \"true\" : null",
              "note": ""
            },
            {
              "attribute": "aria-required",
              "kind": "attribute",
              "bound": "required() ? \"true\" : null",
              "note": ""
            },
            {
              "attribute": "aria-readonly",
              "kind": "attribute",
              "bound": "readonly() ? \"true\" : null",
              "note": "<code>aria-readonly</code> belongs to the GROUP, not to an option: the <code>radio</code> role does not support it (<code>radiogroup</code> does), and an attribute disallowed for a role is a critical violation, not a cosmetic one (<code>lesson-33</code>)."
            },
            {
              "attribute": "aria-orientation",
              "kind": "attribute",
              "bound": "orientation()",
              "note": ""
            },
            {
              "attribute": "data-pct-orientation",
              "kind": "attribute",
              "bound": "orientation()",
              "note": ""
            },
            {
              "attribute": "data-pct-invalid",
              "kind": "attribute",
              "bound": "showInvalid() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-warning",
              "kind": "attribute",
              "bound": "warningLit() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-disabled",
              "kind": "attribute",
              "bound": "disabled() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-in-field",
              "kind": "attribute",
              "bound": "inField ? \"\" : null",
              "note": ""
            }
          ]
        },
        {
          "name": "PctRadio",
          "kind": "component",
          "selector": "pct-radio",
          "description": "A single option inside pct-radio-group. Not a form control of its own — the group holds the state (req-api-signal-forms). It stands on a native <input type=\"radio\"> with a shared name, so arrow navigation and Tab behaviour come from the browser rather than from a roving-tabindex implementation of ours.",
          "members": [
            {
              "name": "value",
              "kind": "input",
              "required": true,
              "type": "T",
              "default": null,
              "description": "The value this option stands for.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "disabled",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Disables a single option; the group can disable them all.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name of an option whose projected content is not text — an icon, a colour swatch. An INPUT rather than an aria-label written on the tag, because the tag cannot carry one: role=\"radio\" sits on the <input> inside, the host has no role at all, and an ARIA name on a roleless element is ignored (req-a11y-built-in).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabelledby",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "As ariaLabel, for a name that already stands elsewhere on the page. It wins over both.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-radio",
              "note": ""
            },
            {
              "attribute": "data-pct-checked",
              "kind": "attribute",
              "bound": "checked() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-disabled",
              "kind": "attribute",
              "bound": "isDisabled() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-invalid",
              "kind": "attribute",
              "bound": "group.showInvalid() ? \"\" : null",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctRadioOption",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What the group needs to know about an option, and nothing more: the value it stands for and a name to call it by in a message. Declared HERE, beside the group, so that the option can provide it while importing the group — the other direction (contentChildren(PctRadio)) would close the import cycle.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PCT_RADIO_OPTION",
          "kind": "token",
          "selector": null,
          "detail": null,
          "description": "The channel through which pct-radio announces itself to the group.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctRadioGroup",
          "kind": "component",
          "selector": "pct-radio-group",
          "detail": null,
          "description": "A single-choice group. The group is the form control (FormValueControl), not the individual options — from the form's point of view a radiogroup edits one value (req-api-signal-forms).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctRadio",
          "kind": "component",
          "selector": "pct-radio",
          "detail": null,
          "description": "A single option inside pct-radio-group. Not a form control of its own — the group holds the state (req-api-signal-forms). It stands on a native <input type=\"radio\"> with a shared name, so arrow navigation and Tab behaviour come from the browser rather than from a roving-tabindex implementation of ours.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "circle",
          "description": "the ring drawn over it"
        },
        {
          "name": "control",
          "description": "the native input of an option"
        },
        {
          "name": "dot",
          "description": "the mark inside the chosen ring"
        },
        {
          "name": "label",
          "description": "the text of an option"
        },
        {
          "name": "group-error",
          "description": "the message when the group is invalid"
        },
        {
          "name": "group-hint",
          "description": "the hint under the group"
        },
        {
          "name": "group-label",
          "description": "the label of the whole group"
        },
        {
          "name": "group-options",
          "description": "the list of options"
        },
        {
          "name": "group-warning",
          "description": "the message when the choice is allowed and suspect — after the error, before the hint (0087)"
        }
      ],
      "tokens": [
        {
          "name": "--pct-radio-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the radio."
        },
        {
          "name": "--pct-radio-bg-disabled",
          "type": "color",
          "ref": "{pct.surface-disabled}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the radio while disabled."
        },
        {
          "name": "--pct-radio-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the radio."
        },
        {
          "name": "--pct-radio-border-checked",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge colour of the radio when checked."
        },
        {
          "name": "--pct-radio-border-disabled",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge colour of the radio while disabled."
        },
        {
          "name": "--pct-radio-border-hover",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Edge colour of the radio on hover."
        },
        {
          "name": "--pct-radio-border-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Edge colour of the radio when invalid."
        },
        {
          "name": "--pct-radio-dot-bg",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Background of the dot."
        },
        {
          "name": "--pct-radio-dot-size",
          "type": "dimension",
          "ref": null,
          "light": "8px",
          "dark": "8px",
          "description": "Size of the dot."
        },
        {
          "name": "--pct-radio-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Colour of the dot while disabled."
        },
        {
          "name": "--pct-radio-fg-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Colour of the dot when invalid."
        },
        {
          "name": "--pct-radio-fg-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Text colour of the warning line (0087); the ring keeps its colour, a warning being no fault of the control."
        },
        {
          "name": "--pct-radio-gap",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Gap between the radio and its label."
        },
        {
          "name": "--pct-radio-group-label-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the group label."
        },
        {
          "name": "--pct-radio-hint-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the hint."
        },
        {
          "name": "--pct-radio-label-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the label."
        },
        {
          "name": "--pct-radio-option-gap",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Gap between the options of a group."
        },
        {
          "name": "--pct-radio-size",
          "type": "dimension",
          "ref": null,
          "light": "18px",
          "dark": "18px",
          "description": "Size of the radio."
        },
        {
          "name": "--pct-radio-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Touch-target floor of the radio."
        }
      ],
      "texts": [
        "fieldWarning"
      ],
      "keyboard": "| key             | effect                                        | source                                             |\n| --------------- | --------------------------------------------- | -------------------------------------------------- |\n| `↑` `↓` `←` `→` | move between options, wrapping around         | **native** `<input type=\"radio\">` sharing a `name` |\n| `Tab`           | one stop in the Tab order for the whole group | native                                             |\n\nThis is the model implementation of\n[`req-api-platform`](../requirements/api.md#req-api-platform): **no roving tabindex of our\nown**. Tested in `apps/sandbox-e2e/src/radio.spec.ts`.",
      "decisions": [
        {
          "id": "0005",
          "title": "Signal forms without ControlValueAccessor"
        },
        {
          "id": "0010",
          "title": "A generic value and NoInfer"
        },
        {
          "id": "0003",
          "title": "Wrapper and control"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 91.78,
          "killed": 134,
          "survived": 11,
          "mutants": 146,
          "files": 2
        },
        "e2e": {
          "cases": 7,
          "spec": "apps/sandbox-e2e/src/radio.spec.ts"
        },
        "pairs": 13,
        "baselines": 2,
        "cost": {
          "elements": 18,
          "depth": 5,
          "listeners": 10,
          "renders": 1,
          "micros": 5795,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "switch",
      "classes": [
        "PctSwitch"
      ],
      "role": "switch",
      "category": "Choices",
      "status": "released",
      "entrypoint": "@pacit/components/switch",
      "selectors": [
        "pct-switch"
      ],
      "harnesses": [
        "PctSwitchHarness"
      ],
      "docs": "/components/switch",
      "summary": "A setting that takes effect the moment it is flipped, with no Save button beside it.",
      "usage": {
        "code": "<pct-switch label=\"Wi-Fi\" [(checked)]=\"wifi\" />",
        "lang": "html"
      },
      "examples": [
        {
          "key": "states",
          "title": "On, off, and out of reach",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctSwitch } from '@pacit/components/switch';\n\n/**\n * On, off, and out of reach\n *\n * A switch takes effect at once — there is no Save behind it — so a disabled one says\n * \"this setting is not yours here\" rather than \"fill this in later\".\n */\n@Component({\n  selector: 'demo-switch-states',\n  imports: [PctSwitch],\n  styles: ':host { display: grid; gap: 12px; }',\n  template: `\n    <pct-switch\n      label=\"Notifications\"\n      hint=\"A sound and a badge\"\n      [(checked)]=\"sound\"\n    />\n    <pct-switch\n      label=\"Two-factor sign-in\"\n      hint=\"Managed by your organisation\"\n      [checked]=\"true\"\n      disabled\n    />\n    <pct-switch label=\"Location\" [checked]=\"false\" readonly />\n  `,\n})\nexport class SwitchStatesDemo {\n  readonly sound = signal(true);\n}"
        }
      ],
      "api": [
        {
          "name": "PctSwitch",
          "kind": "component",
          "selector": "pct-switch",
          "description": "Switch. A setting that takes effect the moment it is moved, drawn as a track the thumb travels along — the ARIA APG's Switch pattern, built on the same native <input type=\"checkbox\"> as PctCheckbox and carrying role=\"switch\" over it.",
          "members": [
            {
              "name": "checked",
              "kind": "model",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "On/off — the only required field of the FormCheckboxControl contract.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "disabled",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Blocks the control and greys it — the native disabled, so it leaves the tab order as well. With [formField] the directive writes it, as it writes every input of the FormUiControl contract.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "readonly",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "The switch is a native checkbox, which has no readonly, so the click is swallowed here instead; the control stays focusable, unlike under disabled, and aria-readonly says so.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "invalid",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "The form's verdict; shown only once touched, so an empty form does not open red.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "touched",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the user has left the field once; with invalid it gates the error face.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "required",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Marks the label with the required sign; with [formField] it follows the schema's required().",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "errors",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[]",
              "default": "[]",
              "description": "The form's validation errors; the first one's message takes the hint's place once the field is touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "warnings",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[] | undefined",
              "default": "undefined",
              "description": "A verdict without a veto (0087): shown after the error and before the hint once the field is touched, in the warning tone, with aria-invalid untouched. Left unbound, the control reads what pctWarn() wrote for its own [formField]; bound — [] included — the list given stands.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "name",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The native name — what a form submission calls the value.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "touch",
              "kind": "output",
              "required": false,
              "type": "void",
              "default": null,
              "description": "Emitted on blur — lets the form mark the field as touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "label",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The visible label, rendered by the control itself when it stands outside a pct-field; inside one, the field's label is the name.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "hint",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "A line of help under the control, outside a pct-field; the first error message takes its place while the field is invalid and touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name of a switch with no visible label — an INPUT rather than an aria-label written on the tag, for the same reason as on the checkbox: the role sits on the <input> inside, the host has no role at all, and an ARIA name on a roleless element is ignored (req-a11y-built-in).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabelledby",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "As ariaLabel, for a name that already stands somewhere on the page.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-switch",
              "note": ""
            },
            {
              "attribute": "data-pct-checked",
              "kind": "attribute",
              "bound": "checked() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-invalid",
              "kind": "attribute",
              "bound": "showInvalid() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-warning",
              "kind": "attribute",
              "bound": "warningLit() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-disabled",
              "kind": "attribute",
              "bound": "disabled() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-in-field",
              "kind": "attribute",
              "bound": "inField ? \"\" : null",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctSwitch",
          "kind": "component",
          "selector": "pct-switch",
          "detail": null,
          "description": "Switch. A setting that takes effect the moment it is moved, drawn as a track the thumb travels along — the ARIA APG's Switch pattern, built on the same native <input type=\"checkbox\"> as PctCheckbox and carrying role=\"switch\" over it.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "control",
          "description": "the native input"
        },
        {
          "name": "error",
          "description": "the message when invalid"
        },
        {
          "name": "hint",
          "description": "the hint under the label"
        },
        {
          "name": "label",
          "description": "the label beside the switch"
        },
        {
          "name": "thumb",
          "description": "the knob — painted over the control, and it lets the pointer through to it"
        },
        {
          "name": "track",
          "description": "the pill the thumb travels"
        },
        {
          "name": "warning",
          "description": "the message when the value is allowed and suspect — after the error, before the hint (0087)"
        }
      ],
      "tokens": [
        {
          "name": "--pct-switch-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of the switch while disabled."
        },
        {
          "name": "--pct-switch-fg-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Text colour of the switch when invalid."
        },
        {
          "name": "--pct-switch-fg-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Text colour of the warning line (0087); the track keeps its colour, a warning being no fault of the control."
        },
        {
          "name": "--pct-switch-gap",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Gap between the switch and its label."
        },
        {
          "name": "--pct-switch-height",
          "type": "dimension",
          "ref": null,
          "light": "24px",
          "dark": "24px",
          "description": "Height of the switch."
        },
        {
          "name": "--pct-switch-hint-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the hint."
        },
        {
          "name": "--pct-switch-label-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the label."
        },
        {
          "name": "--pct-switch-padding-x",
          "type": "dimension",
          "ref": null,
          "light": "3px",
          "dark": "3px",
          "description": "Horizontal padding of the switch."
        },
        {
          "name": "--pct-switch-radius",
          "type": "dimension",
          "ref": null,
          "light": "999px",
          "dark": "999px",
          "description": "Corner radius of the switch."
        },
        {
          "name": "--pct-switch-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Touch-target floor of the switch."
        },
        {
          "name": "--pct-switch-thumb-bg",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Background of the thumb."
        },
        {
          "name": "--pct-switch-thumb-bg-checked",
          "type": "color",
          "ref": "{pct.on-primary}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the thumb when checked."
        },
        {
          "name": "--pct-switch-thumb-bg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Background of the thumb while disabled."
        },
        {
          "name": "--pct-switch-thumb-size",
          "type": "dimension",
          "ref": null,
          "light": "16px",
          "dark": "16px",
          "description": "Size of the thumb."
        },
        {
          "name": "--pct-switch-track-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the track."
        },
        {
          "name": "--pct-switch-track-bg-checked",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Background of the track when checked."
        },
        {
          "name": "--pct-switch-track-bg-disabled",
          "type": "color",
          "ref": "{pct.surface-disabled}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the track while disabled."
        },
        {
          "name": "--pct-switch-track-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the track."
        },
        {
          "name": "--pct-switch-track-border-checked",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge colour of the track when checked."
        },
        {
          "name": "--pct-switch-track-border-disabled",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge colour of the track while disabled."
        },
        {
          "name": "--pct-switch-track-border-hover",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Edge colour of the track on hover."
        },
        {
          "name": "--pct-switch-track-border-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Edge colour of the track when invalid."
        },
        {
          "name": "--pct-switch-width",
          "type": "dimension",
          "ref": null,
          "light": "40px",
          "dark": "40px",
          "description": "Width of the switch."
        }
      ],
      "texts": [
        "fieldWarning"
      ],
      "keyboard": "| key     | effect | test                             |\n| ------- | ------ | -------------------------------- |\n| `Space` | toggle | native `<input type=\"checkbox\">` |\n\nNo handling of its own ([`req-api-platform`](../requirements/api.md#req-api-platform)) — and\nthat is the point of the native road: the APG lists `Space` for the pattern and the platform\nalready implements it. `readonly` blocks the change **without losing focusability**.",
      "decisions": [
        {
          "id": "0039",
          "title": "A state the platform publishes is not ours to write"
        },
        {
          "id": "0005",
          "title": "Signal forms without ControlValueAccessor"
        },
        {
          "id": "0003",
          "title": "Wrapper and control"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 96.77,
          "killed": 60,
          "survived": 2,
          "mutants": 62,
          "files": 1
        },
        "e2e": {
          "cases": 7,
          "spec": "apps/sandbox-e2e/src/switch.spec.ts"
        },
        "pairs": 14,
        "baselines": 2,
        "cost": {
          "elements": 7,
          "depth": 4,
          "listeners": 4,
          "renders": 1,
          "micros": 3681,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "chips",
      "classes": [
        "PctChips",
        "PctChip"
      ],
      "role": "a row of chosen values the user can take back",
      "category": "Choices",
      "status": "released",
      "entrypoint": "@pacit/components/chips",
      "selectors": [
        "pct-chips",
        "pct-chip"
      ],
      "harnesses": [
        "PctChipsHarness",
        "PctChipHarness"
      ],
      "docs": "/components/chips",
      "summary": "A row of chosen values the reader can take back, one cross at a time.",
      "usage": {
        "code": "<pct-chips ariaLabel=\"Filters\">\n  <pct-chip removable (removed)=\"drop('Angular')\">Angular</pct-chip>\n</pct-chips>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "sizes",
          "title": "Sizes, and chips that stay",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctChip, PctChips } from '@pacit/components/chips';\n\n/**\n * Sizes, and chips that stay\n *\n * `size` rides the shared axis. A chip without `removable` is a plain word — a tag the\n * reader cannot take back — and never draws the button it would not act on.\n */\n@Component({\n  selector: 'demo-chips-sizes',\n  imports: [PctChip, PctChips],\n  styles: ':host { display: grid; gap: 12px; }',\n  template: `\n    <pct-chips ariaLabel=\"Topics\" size=\"sm\">\n      <pct-chip>Angular</pct-chip>\n      <pct-chip>Signals</pct-chip>\n      <pct-chip>Tokens</pct-chip>\n    </pct-chips>\n    <pct-chips ariaLabel=\"Filters\" size=\"lg\">\n      @for (filter of filters(); track filter) {\n        <pct-chip removable (removed)=\"drop(filter)\">{{ filter }}</pct-chip>\n      }\n    </pct-chips>\n  `,\n})\nexport class ChipsSizesDemo {\n  readonly filters = signal(['Open', 'Assigned to me', 'This week']);\n\n  protected drop(filter: string): void {\n    this.filters.update((all) => all.filter((f) => f !== filter));\n  }\n}"
        }
      ],
      "api": [
        {
          "name": "PctChips",
          "kind": "component",
          "selector": "pct-chips",
          "description": "A row of chosen values the user can take back: the active filters above a table, the recipients of a message.",
          "members": [
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name of the list. Optional, and deliberately without a text key behind it: a list is allowed to be nameless, and a library default would have to guess what the list holds — \"Chips\" names the paint, not the content (0051). Two rows on one page (\"recipients\", \"active filters\") are told apart here.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "size",
              "kind": "input",
              "required": false,
              "type": "PctSize",
              "default": "config.defaultSize",
              "description": "Size of every pill in the row; taken from the global configuration by default.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-chips",
              "note": ""
            },
            {
              "attribute": "role",
              "kind": "static",
              "bound": "list",
              "note": "The role is static, and that is measured rather than assumed: a <code>role=&quot;list&quot;</code> with zero items raises nothing in any engine's audit (0051 quotes the probe), so an empty row needs no machinery — unlike the listbox, whose empty panel is a critical violation and taught the select to keep its role conditional."
            },
            {
              "attribute": "aria-label",
              "kind": "attribute",
              "bound": "ariaLabel() || null",
              "note": ""
            },
            {
              "attribute": "data-pct-size",
              "kind": "attribute",
              "bound": "size()",
              "note": ""
            }
          ]
        },
        {
          "name": "PctChip",
          "kind": "component",
          "selector": "pct-chip",
          "description": "One chosen value in a pct-chips row. Its content is the application's — projected, never generated — and the one thing the chip may add to it is the control that takes the value back.",
          "members": [
            {
              "name": "removable",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the chip draws the control that takes it back — off by default. A mixed row is ordinary: the one filter the view cannot stand without keeps its removable off and shows no button, and the focus repair steps over it.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "removed",
              "kind": "output",
              "required": false,
              "type": "void",
              "default": null,
              "description": "Said when the user presses remove. The collection is the application's, so this is an announcement and not an act: the consumer shortens their own array and the row follows on the next render — or does not, and the chip is correct to stand still.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-chip",
              "note": ""
            },
            {
              "attribute": "role",
              "kind": "static",
              "bound": "listitem",
              "note": "The platform's own name for &quot;one of these&quot; — <code>pct-tab</code>'s move with <code>tabpanel</code>: the native <code>&lt;li&gt;</code> is out of reach on a custom element, so the role is declared on the element that is actually there (<code>req-a11y-built-in</code>)."
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctChips",
          "kind": "component",
          "selector": "pct-chips",
          "detail": null,
          "description": "A row of chosen values the user can take back: the active filters above a table, the recipients of a message.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctChip",
          "kind": "component",
          "selector": "pct-chip",
          "detail": null,
          "description": "One chosen value in a pct-chips row. Its content is the application's — projected, never generated — and the one thing the chip may add to it is the control that takes the value back.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "label",
          "description": "a chip's text"
        },
        {
          "name": "remove",
          "description": "the button that takes the chip back"
        }
      ],
      "tokens": [
        {
          "name": "--pct-chips-item-bg",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of an item."
        },
        {
          "name": "--pct-chips-item-border",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge colour of an item."
        },
        {
          "name": "--pct-chips-item-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of an item."
        },
        {
          "name": "--pct-chips-item-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of an item."
        },
        {
          "name": "--pct-chips-item-font-size-lg",
          "type": "dimension",
          "ref": "{pct.font.size.lg}",
          "light": "16px",
          "dark": "16px",
          "description": "Font size of an item at size lg."
        },
        {
          "name": "--pct-chips-item-font-size-sm",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of an item at size sm."
        },
        {
          "name": "--pct-chips-item-gap",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Gap between a chip's label and its remove button."
        },
        {
          "name": "--pct-chips-item-height",
          "type": "dimension",
          "ref": "{pct.control.height.md}",
          "light": "36px",
          "dark": "36px",
          "description": "Height of an item."
        },
        {
          "name": "--pct-chips-item-height-lg",
          "type": "dimension",
          "ref": "{pct.control.height.lg}",
          "light": "44px",
          "dark": "44px",
          "description": "Height of an item at size lg."
        },
        {
          "name": "--pct-chips-item-height-sm",
          "type": "dimension",
          "ref": "{pct.control.height.sm}",
          "light": "28px",
          "dark": "28px",
          "description": "Height of an item at size sm."
        },
        {
          "name": "--pct-chips-item-padding-x",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of an item."
        },
        {
          "name": "--pct-chips-item-padding-x-lg",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of an item at size lg."
        },
        {
          "name": "--pct-chips-item-padding-x-sm",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Horizontal padding of an item at size sm."
        },
        {
          "name": "--pct-chips-item-radius",
          "type": "dimension",
          "ref": null,
          "light": "999px",
          "dark": "999px",
          "description": "Corner radius of an item."
        },
        {
          "name": "--pct-chips-list-gap",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Gap between chips."
        },
        {
          "name": "--pct-chips-remove-bg-hover",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the remove button on hover."
        },
        {
          "name": "--pct-chips-remove-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the remove button."
        },
        {
          "name": "--pct-chips-remove-fg-hover",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the remove button on hover."
        },
        {
          "name": "--pct-chips-remove-radius",
          "type": "dimension",
          "ref": null,
          "light": "999px",
          "dark": "999px",
          "description": "Corner radius of the remove button."
        },
        {
          "name": "--pct-chips-remove-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Touch-target floor of the remove button."
        }
      ],
      "texts": [
        "chipRemove"
      ],
      "keyboard": "| key                 | effect                                           | test                                                                                                                         |\n| ------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |\n| `Tab` / `Shift+Tab` | moves between the remove buttons, document order | `apps/sandbox-e2e/src/chips.spec.ts` — the pinned chip costs nothing to step over, because there is nothing in it to stop at |\n| `Enter` / `Space`   | presses the remove button                        | `apps/sandbox-e2e/src/chips.spec.ts` — and focus lands on the next cross, in three engines                                   |\n\nBoth rows are the platform's: the component ships **zero key handlers** — no `Delete`, no\narrows, no roving `tabindex` ([`req-api-platform`](../requirements/api.md#req-api-platform)).\nThe serial-removal behaviour Material builds a grid pattern for is delivered here by the\nrepair alone.",
      "decisions": [],
      "evidence": {
        "mutation": {
          "score": 89.47,
          "killed": 51,
          "survived": 6,
          "mutants": 57,
          "files": 1
        },
        "e2e": {
          "cases": 12,
          "spec": "apps/sandbox-e2e/src/chips.spec.ts"
        },
        "pairs": 4,
        "baselines": 2,
        "cost": {
          "elements": 22,
          "depth": 6,
          "listeners": 6,
          "renders": 1,
          "micros": 7174,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "slider",
      "classes": [
        "PctSlider"
      ],
      "role": "slider",
      "category": "Choices",
      "status": "released",
      "entrypoint": "@pacit/components/slider",
      "selectors": [
        "pct-slider"
      ],
      "harnesses": [
        "PctSliderHarness"
      ],
      "docs": "/components/slider",
      "summary": "A position on a numeric continuum, dragged with the pointer or walked with the arrows.",
      "usage": {
        "code": "<pct-slider label=\"Volume\" [(value)]=\"volume\" />",
        "lang": "html"
      },
      "examples": [
        {
          "key": "range",
          "title": "A range of its own, with marks",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctSlider } from '@pacit/components/slider';\n\n/**\n * A range of its own, with marks\n *\n * `min`, `max` and `step` set the scale; `marks` draws a tick per step, so a slider with\n * five stops reads as five stops before the thumb moves.\n */\n@Component({\n  selector: 'demo-slider-range',\n  imports: [PctSlider],\n  styles: ':host { display: grid; gap: 12px; max-inline-size: 28rem; }',\n  template: `\n    <pct-slider\n      label=\"Team size\"\n      hint=\"Seats on the plan\"\n      [(value)]=\"seats\"\n      [min]=\"5\"\n      [max]=\"25\"\n      [step]=\"5\"\n      [marks]=\"true\"\n    />\n    <p>{{ seats() }} seats.</p>\n  `,\n})\nexport class SliderRangeDemo {\n  readonly seats = signal(10);\n}"
        }
      ],
      "api": [
        {
          "name": "PctSlider",
          "kind": "component",
          "selector": "pct-slider",
          "description": "Slider. A position on a numeric continuum, drawn on the platform's own <input type=\"range\"> — the ARIA APG's Slider pattern, in the variant the APG's own HTML example is: the native element, which carries the role, the value, the bounds and the whole keyboard without a line from us (0042).",
          "members": [
            {
              "name": "value",
              "kind": "model",
              "required": false,
              "type": "number",
              "default": "0",
              "description": "The position. A number and never a T: a slider is a place on a numeric continuum, and a control with named steps is that same number with a labels list over it, so req-api-generic is met by NOT being generic.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "disabled",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Blocks the control and greys it — the native disabled, so it leaves the tab order as well. With [formField] the directive writes it, as it writes every input of the FormUiControl contract.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "readonly",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "A native range has no readonly, so a move is undone here instead; the thumb stays focusable, unlike under disabled, and aria-readonly says so.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "invalid",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "The form's verdict; shown only once touched, so an empty form does not open red.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "touched",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the user has left the field once; with invalid it gates the error face.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "required",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Marks the label with the required sign; with [formField] it follows the schema's required().",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "errors",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[]",
              "default": "[]",
              "description": "The form's validation errors; the first one's message takes the hint's place once the field is touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "warnings",
              "kind": "input",
              "required": false,
              "type": "readonly ValidationError.WithOptionalFieldTree[] | undefined",
              "default": "undefined",
              "description": "A verdict without a veto (0087): shown after the error and before the hint once the field is touched, in the warning tone, with aria-invalid untouched. Left unbound, the control reads what pctWarn() wrote for its own [formField]; bound — [] included — the list given stands.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "name",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The native name — what a form submission calls the value.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "min",
              "kind": "input",
              "required": false,
              "type": "unknown",
              "default": "undefined",
              "description": "Value bounds. They belong to the FormUiControl contract, so with [formField] the directive fills them itself from the schema's min() / max() validators — and here they are also the native min / max attributes, so one pair of numbers is both what the form validates and what the platform clamps to.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "max",
              "kind": "input",
              "required": false,
              "type": "unknown",
              "default": "undefined",
              "description": "The upper bound — as min: the form's and the native max in one; 100 when absent.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "step",
              "kind": "input",
              "required": false,
              "type": "number",
              "default": "1",
              "description": "The granularity — and the only snapping there is (<datalist> snaps in no engine).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "touch",
              "kind": "output",
              "required": false,
              "type": "void",
              "default": null,
              "description": "Emitted on blur — lets the form mark the field as touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "label",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The visible label, rendered by the control itself when it stands outside a pct-field; inside one, the field's label is the name.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "hint",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "A line of help under the control, outside a pct-field; the first error message takes its place while the field is invalid and touched.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name of a slider with no visible label — an INPUT rather than an aria-label on the tag, for the same reason as on the switch: the role sits on the <input> inside and an ARIA name on the roleless host is ignored.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabelledby",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "As ariaLabel, for a name that already stands somewhere on the page.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "format",
              "kind": "input",
              "required": false,
              "type": "Intl.NumberFormatOptions | null",
              "default": "null",
              "description": "How the value is SAID — Intl.NumberFormat options. Given, it turns on the visible bubble and aria-valuetext; absent, the platform's bare aria-valuenow is the whole announcement, which is what a plain 0–100 slider wants.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "labels",
              "kind": "input",
              "required": false,
              "type": "readonly string[]",
              "default": "[]",
              "description": "Named steps (\"Small\", \"Medium\", \"Large\"), indexed from min by step. It wins over format: a name is what the reader gets and the number underneath is the value.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "marks",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Ticks at every step. Our own drawing — <datalist> renders none in firefox.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "orientation",
              "kind": "input",
              "required": false,
              "type": "PctSliderOrientation",
              "default": "'horizontal'",
              "description": "Which axis the slider runs along. Vertical is writing-mode, so no rule reads the direction.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "locale",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "Overrides the application's LOCALE_ID for the formatted value, as on [pctNumber].",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-slider",
              "note": ""
            },
            {
              "attribute": "data-pct-orientation",
              "kind": "attribute",
              "bound": "orientation()",
              "note": ""
            },
            {
              "attribute": "data-pct-invalid",
              "kind": "attribute",
              "bound": "showInvalid() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-warning",
              "kind": "attribute",
              "bound": "warningLit() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-disabled",
              "kind": "attribute",
              "bound": "disabled() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-in-field",
              "kind": "attribute",
              "bound": "inField ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "[style.--_pct-slider-fraction]",
              "kind": "property",
              "bound": "fraction()",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctSliderOrientation",
          "kind": "type",
          "selector": null,
          "detail": "'horizontal' | 'vertical'",
          "description": "Which way the slider runs. An input rather than a tag — the value type is a number in both (0042).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctSlider",
          "kind": "component",
          "selector": "pct-slider",
          "detail": null,
          "description": "Slider. A position on a numeric continuum, drawn on the platform's own <input type=\"range\"> — the ARIA APG's Slider pattern, in the variant the APG's own HTML example is: the native element, which carries the role, the value, the bounds and the whole keyboard without a line from us (0042).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "bubble",
          "description": "the value shown while dragging"
        },
        {
          "name": "control",
          "description": "the native range input"
        },
        {
          "name": "error",
          "description": "the message when invalid"
        },
        {
          "name": "fill",
          "description": "the part up to the thumb"
        },
        {
          "name": "hint",
          "description": "the hint under the control"
        },
        {
          "name": "label",
          "description": "the label of the control"
        },
        {
          "name": "mark",
          "description": "one tick along the track"
        },
        {
          "name": "thumb",
          "description": "the handle"
        },
        {
          "name": "track",
          "description": "the full length"
        },
        {
          "name": "warning",
          "description": "the message when the value is allowed and suspect — after the error, before the hint (0087)"
        }
      ],
      "tokens": [
        {
          "name": "--pct-slider-bubble-bg",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the value bubble."
        },
        {
          "name": "--pct-slider-bubble-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the value bubble."
        },
        {
          "name": "--pct-slider-bubble-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of the value bubble."
        },
        {
          "name": "--pct-slider-bubble-padding-x",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Horizontal padding of the value bubble."
        },
        {
          "name": "--pct-slider-bubble-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the value bubble."
        },
        {
          "name": "--pct-slider-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of the slider while disabled."
        },
        {
          "name": "--pct-slider-fg-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Text colour of the slider when invalid."
        },
        {
          "name": "--pct-slider-fg-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Text colour of the warning line (0087); the thumb keeps its colour, a warning being no fault of the control."
        },
        {
          "name": "--pct-slider-fill-bg",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Background of the fill."
        },
        {
          "name": "--pct-slider-fill-bg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Background of the fill while disabled."
        },
        {
          "name": "--pct-slider-gap",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Gap between the slider and its label."
        },
        {
          "name": "--pct-slider-height",
          "type": "dimension",
          "ref": null,
          "light": "10rem",
          "dark": "10rem",
          "description": "Height of the slider."
        },
        {
          "name": "--pct-slider-hint-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the hint."
        },
        {
          "name": "--pct-slider-label-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the label."
        },
        {
          "name": "--pct-slider-mark-bg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Background of the mark."
        },
        {
          "name": "--pct-slider-mark-size",
          "type": "dimension",
          "ref": null,
          "light": "2px",
          "dark": "2px",
          "description": "Size of the mark."
        },
        {
          "name": "--pct-slider-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Touch-target floor of the slider."
        },
        {
          "name": "--pct-slider-thumb-bg",
          "type": "color",
          "ref": "{pct.on-primary}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the thumb."
        },
        {
          "name": "--pct-slider-thumb-bg-disabled",
          "type": "color",
          "ref": "{pct.surface-disabled}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the thumb while disabled."
        },
        {
          "name": "--pct-slider-thumb-border",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge colour of the thumb."
        },
        {
          "name": "--pct-slider-thumb-border-disabled",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge colour of the thumb while disabled."
        },
        {
          "name": "--pct-slider-thumb-border-hover",
          "type": "color",
          "ref": "{pct.primary-hover}",
          "light": "#1d4ed8",
          "dark": "#93c5fd",
          "description": "Edge colour of the thumb on hover."
        },
        {
          "name": "--pct-slider-thumb-border-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Edge colour of the thumb when invalid."
        },
        {
          "name": "--pct-slider-thumb-size",
          "type": "dimension",
          "ref": null,
          "light": "18px",
          "dark": "18px",
          "description": "Size of the thumb."
        },
        {
          "name": "--pct-slider-track-bg",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Background of the track."
        },
        {
          "name": "--pct-slider-track-bg-disabled",
          "type": "color",
          "ref": "{pct.surface-disabled}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the track while disabled."
        },
        {
          "name": "--pct-slider-track-height",
          "type": "dimension",
          "ref": null,
          "light": "6px",
          "dark": "6px",
          "description": "Height of the track."
        },
        {
          "name": "--pct-slider-track-radius",
          "type": "dimension",
          "ref": null,
          "light": "999px",
          "dark": "999px",
          "description": "Corner radius of the track."
        }
      ],
      "texts": [
        "fieldWarning"
      ],
      "keyboard": "| key                       | effect          | test                          |\n| ------------------------- | --------------- | ----------------------------- |\n| `ArrowRight` / `ArrowUp`  | one `step` up   | native `<input type=\"range\">` |\n| `ArrowLeft` / `ArrowDown` | one `step` down | native                        |\n| `PageUp` / `PageDown`     | ten steps       | native                        |\n| `Home` / `End`            | `min` / `max`   | native                        |\n\nNo handling of its own ([`req-api-platform`](../requirements/api.md#req-api-platform)) — the\nwhole map is the platform's and it is **byte-identical in three engines**, which is a claim\nabout three of them and so measured in three. `readonly` blocks the change **without losing\nfocusability**; `disabled` drops the control from the Tab order, which is again the\nplatform's doing.",
      "decisions": [
        {
          "id": "0042",
          "title": "A slider is the platform's range, and only the value it cannot pronounce is ours"
        },
        {
          "id": "0039",
          "title": "A state the platform publishes is not ours to write"
        },
        {
          "id": "0034",
          "title": "Multiplicity is a type, so it is a tag"
        },
        {
          "id": "0005",
          "title": "Signal forms without ControlValueAccessor"
        },
        {
          "id": "0003",
          "title": "Wrapper and control"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 80.11,
          "killed": 149,
          "survived": 36,
          "mutants": 186,
          "files": 1
        },
        "e2e": {
          "cases": 10,
          "spec": "apps/sandbox-e2e/src/slider.spec.ts"
        },
        "pairs": 16,
        "baselines": 2,
        "cost": {
          "elements": 20,
          "depth": 5,
          "listeners": 3,
          "renders": 1,
          "micros": 8351,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "calendar",
      "classes": [
        "PctCalendar"
      ],
      "role": "calendar",
      "category": "Choices",
      "status": "released",
      "entrypoint": "@pacit/components/date",
      "selectors": [
        "pct-calendar"
      ],
      "harnesses": [
        "PctCalendarHarness"
      ],
      "docs": "/components/calendar",
      "summary": "One month of days as a grid, standing on the page rather than folded into a panel.",
      "usage": {
        "code": "<pct-calendar [(value)]=\"day\" ariaLabel=\"Pick a day\" />",
        "lang": "html"
      },
      "examples": [
        {
          "key": "locale",
          "title": "Another language, another first day",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctCalendar, PctDay } from '@pacit/components/date';\n\n/**\n * Another language, another first day\n *\n * `locale` names the month and the weekdays through the platform's own `Intl`; `firstDayOfWeek`\n * starts the row where that culture starts it — Monday is 1.\n */\n@Component({\n  selector: 'demo-calendar-locale',\n  imports: [PctCalendar],\n  styles: ':host { display: block; max-inline-size: 24rem; }',\n  template: `\n    <pct-calendar\n      [(value)]=\"day\"\n      locale=\"pl\"\n      [firstDayOfWeek]=\"1\"\n      ariaLabel=\"Choose a day\"\n    />\n  `,\n})\nexport class CalendarLocaleDemo {\n  readonly day = signal<PctDay | null>(null);\n}"
        }
      ],
      "api": [
        {
          "name": "PctCalendar",
          "kind": "component",
          "selector": "pct-calendar",
          "description": "Calendar — one month of days as a role=\"grid\", walked by a roving tabindex.",
          "members": [
            {
              "name": "value",
              "kind": "model",
              "required": false,
              "type": "PctDay | null",
              "default": "null",
              "description": "The chosen day, or null.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "min",
              "kind": "input",
              "required": false,
              "type": "unknown",
              "default": "undefined",
              "description": "The earliest and latest day the walk may reach; absent means no bound on that side.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "max",
              "kind": "input",
              "required": false,
              "type": "unknown",
              "default": "undefined",
              "description": "The latest day the walk may reach; absent means no bound on that side — the other half of min.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "dateDisabled",
              "kind": "input",
              "required": false,
              "type": "PctDayPredicate | null",
              "default": "null",
              "description": "Days inside the bounds that still cannot be picked. The split is deliberate: min/max are the range and this is the holes in it — the bounds clamp where the keyboard can go, a refused day is reached, announced and simply not takeable.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "startAt",
              "kind": "input",
              "required": false,
              "type": "unknown",
              "default": "undefined",
              "description": "The day the grid opens on while there is no value — absent, today. It is where the walk STARTS and nothing more: no day is marked chosen by it, and the bounds still hold it. The datetime field hands it the month a half that is not a date names, so 31.02.2026 opens on February (0090 §2).",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "disabled",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Freezes the grid: no day can be picked, the walk and the month buttons stop, and aria-disabled says so.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "locale",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "Overrides the application's LOCALE_ID, as on [pctNumber] and <pct-slider>.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "firstDayOfWeek",
              "kind": "input",
              "required": false,
              "type": "number",
              "default": "0",
              "description": "Which day the week starts on, 1 (Monday) … 7 (Sunday). Absent, the locale decides — through the platform where it answers and through a table of regions where it does not (locale.ts — firefox 151 is one engine that does not, and Angular still supports it).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "size",
              "kind": "input",
              "required": false,
              "type": "PctSize",
              "default": "config.defaultSize",
              "description": "Scales the grid with the field sizes, so a panel matches the field it opens from; from providePctConfig by default (req-api-config).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name of the grid; the panel that owns one passes its own.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabelledby",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "As ariaLabel, for a name that already stands somewhere on the page.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "dayPicked",
              "kind": "output",
              "required": false,
              "type": "PctDay",
              "default": null,
              "description": "A day was chosen by the user — which is not the same event as value changing, and the difference is what closes a panel: a value written from outside must not.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-calendar",
              "note": ""
            },
            {
              "attribute": "data-pct-size",
              "kind": "attribute",
              "bound": "size()",
              "note": ""
            },
            {
              "attribute": "data-pct-disabled",
              "kind": "attribute",
              "bound": "disabled() ? \"\" : null",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctCalendarCell",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "One cell of the grid, with everything the template and the reader need already decided.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDayPredicate",
          "kind": "type",
          "selector": null,
          "detail": "(day: PctDay) => boolean",
          "description": "A day the consumer refuses — weekends, holidays, a taken slot.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctCalendar",
          "kind": "component",
          "selector": "pct-calendar",
          "detail": null,
          "description": "Calendar — one month of days as a role=\"grid\", walked by a roving tabindex.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDatePanelAlign",
          "kind": "type",
          "selector": null,
          "detail": "'start' | 'end'",
          "description": "Which edge of the field the panel lines its own up with.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDate",
          "kind": "component",
          "selector": "pct-date",
          "detail": null,
          "description": "Date field — a text control the library formats and parses per locale, with a calendar in a panel beside it.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDayBound",
          "kind": "type",
          "selector": null,
          "detail": "PctDay | undefined | LogicFn<TValue, PctDay | undefined, TPathKind>",
          "description": "A bound pctDayBounds takes: a day, or a function of the field's context that returns one — the two shapes Angular's own min() takes. One that is not a day, undefined among them, is no bound, as it is on the field's input.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctDayBoundError",
          "kind": "type",
          "selector": null,
          "detail": "| { readonly kind: 'minDay'",
          "description": "What pctDayBounds reports: minDay for a day before min, maxDay for one after max. Each carries the bound it broke, as the bound was written.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctDayBoundsConfig",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The bounds of pctDayBounds, and the sentence its errors carry — one for both, or one written from the error, which holds the bound.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctDayBounds",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Bounds a date field in a signal form: what Angular's minDate() and maxDate() do for a Date, for a PctDay — which they cannot take (their path is a Date's, and their validator reads the value with getTime()), and which the compiler refuses as an attribute beside [formField] (NG8022). The bounds reach the field's min / max, so the calendar's walk stops at them, and the form grades the value: a day outside is minDay or maxDay.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctDay",
          "kind": "type",
          "selector": null,
          "detail": "string",
          "description": "A calendar day, written the way the platform already writes one: YYYY-MM-DD.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDayParts",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The three fields of a day, as numbers. month is 1–12, the way a person says it.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctDaysInMonth",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "How many days February has — the Gregorian rule, written once.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "isPctDay",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Whether a string is a day this library will work with: the right shape and a day the calendar has. 2026-02-30 has the shape and is not a day, and the difference matters — a value read from a server, a query string or a localStorage entry is a string somebody else wrote.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctDay",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Builds a day from its three fields, normalising an overflow the way a person expects — month 13 is January of the next year, day 0 is the last day of the previous month. That is what makes pctAddMonths and pctAddDays one line each instead of three branches.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctDayAsUtc",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The day as the one Date anything outside this file is allowed to see: midnight UTC, which is what Intl.DateTimeFormat with timeZone: 'UTC' reads back as the same three fields it was built from. Handing out a local-time Date is the defect this whole module exists to refuse.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctDayParts",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The three fields of a day. The caller has already established that it is one.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctToday",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Today, read from the LOCAL clock — the one read of local time in this file, and the only one that belongs: \"what day is it\" is a question about where the user is standing, and every other question here is arithmetic on a day that already has an answer.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctAddDays",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "n days on (or back).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctAddMonths",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "n months on (or back), clamped to the length of the month it lands in: 31 January plus one month is 28 February and not 3 March. The clamp is what a calendar's \"next month\" button means — a walk that changed the day of the month would move the cursor twice.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctCompareDays",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "-1, 0 or 1 — read from the three fields, though the shape invites a string compare and carried one for months.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctWeekday",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The ISO weekday: Monday is 1, Sunday is 7 — the numbering Intl.Locale.prototype.getWeekInfo() answers in, so the two never need converting between them.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctClampDay",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Held inside the bounds, either of which may be absent — and undefined is what absent is, because that is how the FormUiControl contract's min / max spell an absent bound.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctMonthGrid",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The grid of one month: whole weeks, starting on firstDayOfWeek, with the days of the neighbouring months that share those weeks. Six weeks always, and that is a decision rather than an oversight — a month spans four to six weeks depending on where it starts, and a panel that changed height between March and August would move the page under the pointer mid-walk.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDayField",
          "kind": "type",
          "selector": null,
          "detail": "'day' | 'month' | 'year'",
          "description": "Which of the three fields a position in the format holds.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDayLetters",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The letters a format hint is written with — one per field, from PCT_TEXTS.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctFirstDayOfWeek",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Which day the week starts on, 1 (Monday) … 7 (Sunday).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDayFormat",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "Everything about writing and reading a day in one language, built once per locale.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctDayFormat",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The day, weekday and month names of a locale, built once and shared.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctWeekdayName",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The two names a column header needs: the one the eye reads and the one a reader says.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctWeekdayNames",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The seven column headings, rotated so that the first is firstDayOfWeek.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "pctMonthCaption",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The month and year over the grid (\"December 2026\"), in the value's own calendar.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "caption",
          "description": "the month and the year over the grid"
        },
        {
          "name": "day",
          "description": "one day cell"
        },
        {
          "name": "grid",
          "description": "the grid of days"
        },
        {
          "name": "nav",
          "description": "the previous-month and next-month buttons"
        },
        {
          "name": "week",
          "description": "one row of the grid"
        },
        {
          "name": "weekday",
          "description": "a heading over a column of days"
        },
        {
          "name": "control",
          "description": "the typed input"
        },
        {
          "name": "error",
          "description": "the message when the value is invalid"
        },
        {
          "name": "hint",
          "description": "the hint under the input"
        },
        {
          "name": "label",
          "description": "the label of the typed input"
        },
        {
          "name": "panel",
          "description": "the calendar, floating or inline"
        },
        {
          "name": "toggle",
          "description": "the button that opens the calendar"
        },
        {
          "name": "warning",
          "description": "the message when the value is allowed and suspect — after the error, before the hint (0087)"
        }
      ],
      "tokens": [
        {
          "name": "--pct-date-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the date field."
        },
        {
          "name": "--pct-date-bg-disabled",
          "type": "color",
          "ref": "{pct.surface-disabled}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the date field while disabled."
        },
        {
          "name": "--pct-date-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the date field."
        },
        {
          "name": "--pct-date-border-disabled",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge colour of the date field while disabled."
        },
        {
          "name": "--pct-date-border-focus",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge colour of the date field when focused."
        },
        {
          "name": "--pct-date-border-hover",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Edge colour of the date field on hover."
        },
        {
          "name": "--pct-date-border-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Edge colour of the date field when invalid."
        },
        {
          "name": "--pct-date-border-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Edge colour of the date field under a warning (0087)."
        },
        {
          "name": "--pct-date-caption-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of the month caption."
        },
        {
          "name": "--pct-date-caption-font-weight",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of the month caption."
        },
        {
          "name": "--pct-date-day-bg-hover",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of a day cell on hover."
        },
        {
          "name": "--pct-date-day-bg-selected",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Background of a day cell when selected."
        },
        {
          "name": "--pct-date-day-border-today",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge colour of a day cell for today."
        },
        {
          "name": "--pct-date-day-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of a day cell while disabled."
        },
        {
          "name": "--pct-date-day-fg-muted",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of a day outside the month shown."
        },
        {
          "name": "--pct-date-day-fg-selected",
          "type": "color",
          "ref": "{pct.on-primary}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Text colour of a day cell when selected."
        },
        {
          "name": "--pct-date-day-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of a day cell."
        },
        {
          "name": "--pct-date-day-font-size-lg",
          "type": "dimension",
          "ref": "{pct.font.size.lg}",
          "light": "16px",
          "dark": "16px",
          "description": "Font size of a day cell at size lg."
        },
        {
          "name": "--pct-date-day-font-size-sm",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of a day cell at size sm."
        },
        {
          "name": "--pct-date-day-font-weight-selected",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of a day cell when selected."
        },
        {
          "name": "--pct-date-day-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of a day cell."
        },
        {
          "name": "--pct-date-day-size",
          "type": "dimension",
          "ref": null,
          "light": "32px",
          "dark": "32px",
          "description": "Size of a day cell."
        },
        {
          "name": "--pct-date-day-size-lg",
          "type": "dimension",
          "ref": null,
          "light": "36px",
          "dark": "36px",
          "description": "Size of a day cell at size lg."
        },
        {
          "name": "--pct-date-day-size-sm",
          "type": "dimension",
          "ref": null,
          "light": "28px",
          "dark": "28px",
          "description": "Size of a day cell at size sm."
        },
        {
          "name": "--pct-date-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the date field."
        },
        {
          "name": "--pct-date-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of the date field while disabled."
        },
        {
          "name": "--pct-date-fg-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Text colour of the date field when invalid."
        },
        {
          "name": "--pct-date-fg-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Text colour of the warning line (0087)."
        },
        {
          "name": "--pct-date-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of the date field."
        },
        {
          "name": "--pct-date-font-size-lg",
          "type": "dimension",
          "ref": "{pct.font.size.lg}",
          "light": "16px",
          "dark": "16px",
          "description": "Font size of the date field at size lg."
        },
        {
          "name": "--pct-date-font-size-sm",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of the date field at size sm."
        },
        {
          "name": "--pct-date-gap",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Gap between the label, the input and the hint."
        },
        {
          "name": "--pct-date-hint-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the hint."
        },
        {
          "name": "--pct-date-icon-size",
          "type": "dimension",
          "ref": null,
          "light": "16px",
          "dark": "16px",
          "description": "Size of the icon."
        },
        {
          "name": "--pct-date-label-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the label."
        },
        {
          "name": "--pct-date-nav-bg-hover",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the month navigation on hover."
        },
        {
          "name": "--pct-date-nav-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the month navigation."
        },
        {
          "name": "--pct-date-nav-size",
          "type": "dimension",
          "ref": null,
          "light": "28px",
          "dark": "28px",
          "description": "Size of the month navigation."
        },
        {
          "name": "--pct-date-padding-x",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of the date field."
        },
        {
          "name": "--pct-date-padding-x-lg",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of the date field at size lg."
        },
        {
          "name": "--pct-date-padding-x-sm",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of the date field at size sm."
        },
        {
          "name": "--pct-date-padding-y",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Vertical padding of the date field."
        },
        {
          "name": "--pct-date-padding-y-lg",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Vertical padding of the date field at size lg."
        },
        {
          "name": "--pct-date-padding-y-sm",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Vertical padding of the date field at size sm."
        },
        {
          "name": "--pct-date-panel-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the panel."
        },
        {
          "name": "--pct-date-panel-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the panel."
        },
        {
          "name": "--pct-date-panel-padding-x",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of the panel."
        },
        {
          "name": "--pct-date-panel-padding-y",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Vertical padding of the panel."
        },
        {
          "name": "--pct-date-panel-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the panel."
        },
        {
          "name": "--pct-date-panel-shadow",
          "type": "shadow",
          "ref": "{pct.shadow-panel}",
          "light": "0 8px 24px rgba(15, 23, 42, 0.14)",
          "dark": "0 8px 24px rgba(0, 0, 0, 0.55)",
          "description": "Shadow of the panel."
        },
        {
          "name": "--pct-date-placeholder-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the placeholder."
        },
        {
          "name": "--pct-date-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the date field."
        },
        {
          "name": "--pct-date-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Touch-target floor of the date field."
        },
        {
          "name": "--pct-date-toggle-bg-hover",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the calendar toggle on hover."
        },
        {
          "name": "--pct-date-toggle-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the calendar toggle."
        },
        {
          "name": "--pct-date-toggle-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the calendar toggle."
        },
        {
          "name": "--pct-date-toggle-size",
          "type": "dimension",
          "ref": null,
          "light": "20px",
          "dark": "20px",
          "description": "Size of the calendar toggle."
        },
        {
          "name": "--pct-date-weekday-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of a weekday heading."
        },
        {
          "name": "--pct-date-weekday-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of a weekday heading."
        },
        {
          "name": "--pct-date-weekday-font-weight",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of a weekday heading."
        }
      ],
      "texts": [
        "dateDayLetter",
        "dateMalformed",
        "dateMonthLetter",
        "dateNextMonth",
        "dateOpen",
        "datePreviousMonth",
        "dateYearLetter",
        "fieldWarning"
      ],
      "keyboard": "| key                               | effect                                        | test                                        |\n| --------------------------------- | --------------------------------------------- | ------------------------------------------- |\n| `ArrowRight` / `ArrowLeft`        | one day along the **inline** axis             | `libs/components/date/src/calendar.spec.ts` |\n| `ArrowDown` / `ArrowUp`           | one week                                      | `libs/components/date/src/calendar.spec.ts` |\n| `Home` / `End`                    | the first / last day of the week it stands in | `libs/components/date/src/calendar.spec.ts` |\n| `PageDown` / `PageUp`             | one month                                     | `libs/components/date/src/calendar.spec.ts` |\n| `Shift+PageDown` / `Shift+PageUp` | one year                                      | `apps/sandbox-e2e/src/date.spec.ts`         |\n| `Enter` / `Space`                 | takes the day the cursor is on                | `libs/components/date/src/calendar.spec.ts` |\n\n`ArrowRight` moves to the **next** day in an LTR grid and to the **previous** one in an RTL\ngrid, because a grid's arrows are about the cell to the side and which side that is depends\non the direction it is written in — `req-token-logical` read on the keyboard rather than in a\nstylesheet. Measured as geometry and as behaviour in `apps/sandbox-e2e/src/date.spec.ts`.\n\n**A walk is not a choice.** Moving the cursor never writes the value; only `Enter`, the space\nbar or a press does.",
      "decisions": [
        {
          "id": "0043",
          "title": "A day is not an instant, and the field that takes one is text the application formats"
        },
        {
          "id": "0032",
          "title": "A menu moves focus, a listbox points at it"
        },
        {
          "id": "0026",
          "title": "One channel per politeness, and a message with a home announces from it"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 75.98,
          "killed": 136,
          "survived": 41,
          "mutants": 179,
          "files": 1
        },
        "e2e": {
          "cases": 20,
          "spec": "apps/sandbox-e2e/src/date.spec.ts"
        },
        "pairs": 0,
        "baselines": 0,
        "cost": {
          "elements": 84,
          "depth": 6,
          "listeners": 46,
          "renders": 1,
          "micros": 24841,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "columns",
      "classes": [
        "PctTimeColumns"
      ],
      "role": "time columns",
      "category": "Choices",
      "status": "released",
      "entrypoint": "@pacit/components/time",
      "selectors": [
        "pct-time-columns"
      ],
      "harnesses": [
        "PctTimeColumnsHarness"
      ],
      "docs": "/components/columns",
      "summary": "A time of day as columns of hours and minutes, standing on the page rather than folded into a panel.",
      "usage": {
        "code": "<pct-time-columns [(value)]=\"time\" ariaLabel=\"Meeting time\" />",
        "lang": "html"
      },
      "examples": [
        {
          "key": "clock",
          "title": "The clock and the step",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctTimeColumns, PctTimeOfDay } from '@pacit/components/time';\n\n/**\n * The clock and the step\n *\n * The columns are the field's language: a Korean clock puts the half of the day first, in its\n * own words. And the step decides which rows exist — a quarter hour lists four minutes.\n */\n@Component({\n  selector: 'demo-columns-clock',\n  imports: [PctTimeColumns],\n  styles: ':host { display: flex; flex-wrap: wrap; gap: 24px; }',\n  template: `\n    <pct-time-columns [(value)]=\"korean\" locale=\"ko-KR\" ariaLabel=\"Korean\" />\n    <pct-time-columns\n      [(value)]=\"quarter\"\n      locale=\"en-GB\"\n      step=\"900\"\n      ariaLabel=\"By the quarter\"\n    />\n  `,\n})\nexport class ColumnsClockDemo {\n  readonly korean = signal<PctTimeOfDay | null>('19:30');\n  readonly quarter = signal<PctTimeOfDay | null>('09:45');\n}"
        }
      ],
      "api": [
        {
          "name": "PctTimeColumns",
          "kind": "component",
          "selector": "pct-time-columns",
          "description": "Time columns — one role=\"listbox\" per field of a time, side by side: hours and minutes, seconds where the step has them, and the two halves of the day where the clock has twelve hours (0086 §4).",
          "members": [
            {
              "name": "value",
              "kind": "model",
              "required": false,
              "type": "PctTimeOfDay | null",
              "default": "null",
              "description": "The chosen time, or null. Every movement in a column writes it.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "min",
              "kind": "input",
              "required": false,
              "type": "unknown",
              "default": "undefined",
              "description": "The earliest time a row may stand for; absent means no bound on that side. Later than max, the two are a window across midnight.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "max",
              "kind": "input",
              "required": false,
              "type": "unknown",
              "default": "undefined",
              "description": "The latest time a row may stand for; absent means no bound on that side — the other half of min.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "step",
              "kind": "input",
              "required": false,
              "type": "number",
              "default": "60",
              "description": "The step in seconds, counted from min — 60 by default, as the native attribute's. A step the columns cannot list (one that does not divide a minute, an hour or a day) is refused with a dev-mode warning and read as 60.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "timeDisabled",
              "kind": "input",
              "required": false,
              "type": "PctTimePredicate | null",
              "default": "null",
              "description": "Times inside the bounds that still cannot be taken — the slots already booked on the day a datetime field's panel stands on, which is the control that asks (0090 §5). It is asked of every time on the step, in the value's shape, and a refused time is a row drawn disabled and skipped by the walk; an hour or a half of the day is refused when every time in it is. A value it refuses is kept: the walk starts from the nearest time it lets through.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "disabled",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Freezes the columns: no row can be taken, the walk stops, and aria-disabled says so.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "locale",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "Overrides the application's LOCALE_ID, as on <pct-time> — the clock, the digits and the day-period words follow it.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "size",
              "kind": "input",
              "required": false,
              "type": "PctSize",
              "default": "config.defaultSize",
              "description": "Scales the rows with the field sizes, so a panel matches the field it opens from; from providePctConfig by default (req-api-config).",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name of the group the columns stand in; a panel that owns them names itself instead.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "ariaLabelledby",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "As ariaLabel, for a name that already stands somewhere on the page.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "timePicked",
              "kind": "output",
              "required": false,
              "type": "PctTimeOfDay",
              "default": null,
              "description": "A time was chosen by the user — Enter or Space in a column — which is not the same event as value changing: every movement changes the value, and only this closes a panel.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-time-columns",
              "note": ""
            },
            {
              "attribute": "role",
              "kind": "static",
              "bound": "group",
              "note": ""
            },
            {
              "attribute": "aria-label",
              "kind": "attribute",
              "bound": "ariaLabel() || null",
              "note": ""
            },
            {
              "attribute": "aria-labelledby",
              "kind": "attribute",
              "bound": "ariaLabelledby() || null",
              "note": ""
            },
            {
              "attribute": "data-pct-size",
              "kind": "attribute",
              "bound": "size()",
              "note": ""
            },
            {
              "attribute": "data-pct-disabled",
              "kind": "attribute",
              "bound": "disabled() ? \"\" : null",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctHourCycle",
          "kind": "type",
          "selector": null,
          "detail": "'h11' | 'h12' | 'h23' | 'h24'",
          "description": "The four clocks Intl names. h11 and h12 are twelve-hour clocks that write the first hour 0 and 12; h23 and h24 are twenty-four-hour clocks that write it 0 and 24. The value is 00:05 under all four — a cycle is how a time is written, never which time it is.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeField",
          "kind": "type",
          "selector": null,
          "detail": "'hour' | 'minute' | 'second' | 'dayPeriod'",
          "description": "Which part of the written time a position holds.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeLetters",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The letters a format hint is written with — one per field, from PCT_TEXTS, because they are words of the application's language and not facts about the locale (0086 §3).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeFormat",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "Everything about writing and reading a time in one language, built once per locale.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctTimeFormat",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "How a time is written and read in one language, built once and shared.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeOfDay",
          "kind": "type",
          "selector": null,
          "detail": "string",
          "description": "A time of day, written the way the platform already writes one: HH:mm, or HH:mm:ss where the field's step has seconds in it.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeOfDayParts",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The three fields of a time, as numbers on a 24-hour clock. second is 0 for a time written without seconds — the two shapes are one time.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "isPctTimeOfDay",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Whether a string is a time this library will work with: 00:00 to 23:59:59, two digits a field. A value read from a server, a query string or localStorage is a string somebody else wrote, and three of the strings they write are refused on purpose:",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctTimeOfDay",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Builds a time from its fields: HH:mm, or HH:mm:ss when second is given — 0 included, because the shape is the caller's to choose.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctTimeOfDayParts",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The three fields of a time. The caller has already established that it is one.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctNow",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The time on the LOCAL clock — the one read of local time in this file, and the only one that belongs: \"what time is it\" is a question about where the user is standing, and every other question here is arithmetic on a time that already has an answer.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctAddSeconds",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "n seconds on (or back), and the clock comes round: 23:59:30 plus 45 seconds is 00:00:15. The result keeps the shape it was given unless it has seconds that shape cannot carry — 13:05 plus an hour is 14:05, plus 30 seconds is 13:05:30.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctAddMinutes",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "n minutes on (or back), and the clock comes round: 23:30 plus 45 minutes is 00:15. The shape is kept — a whole minute never moves the seconds.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctCompareTimes",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "-1, 0 or 1, read as seconds since midnight — so 13:05 and 13:05:00, one time in two shapes, compare equal.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctClampTime",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Held inside the bounds, either of which may be absent — and undefined is what absent is, because that is what the FormUiControl contract's min / max are.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "isPctTimeStep",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Whether step — in seconds, like the native attribute — is one the columns can list: a whole number of seconds that divides a minute, a whole number of minutes that divides an hour, or a whole number of hours that divides a day. Any other — 420 seconds, seven-minute slots — makes the valid minutes depend on the hour, which columns that are fields cannot show (0086 §5).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctTimeOnStep",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Whether a time stands on the step, counted from base — the field's min when it has one, as the native element counts (0086, A11).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctSnapToStep",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "The time on the step nearest to this one, counted from base, with the clock coming round: at an hour step 23:40 is 00:00. Exactly halfway goes up, as Math.round does. The shape is kept unless the step puts seconds where it had none.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeRow",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "One row of a column, with everything the template and the walk need already decided.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeColumn",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "One column — one field of the time, one role=\"listbox\", one tab stop.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimePredicate",
          "kind": "type",
          "selector": null,
          "detail": "(time: PctTimeOfDay) => boolean",
          "description": "A predicate on a time of day: true refuses it. The columns ask it of every time on the step, once per change of the step, the bounds or the predicate itself.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeColumns",
          "kind": "component",
          "selector": "pct-time-columns",
          "detail": null,
          "description": "Time columns — one role=\"listbox\" per field of a time, side by side: hours and minutes, seconds where the step has them, and the two halves of the day where the clock has twelve hours (0086 §4).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimePanelAlign",
          "kind": "type",
          "selector": null,
          "detail": "'start' | 'end'",
          "description": "Which edge of the field the panel lines its own up with.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTime",
          "kind": "component",
          "selector": "pct-time",
          "detail": null,
          "description": "Time field — a text control the library formats and parses per locale, with columns of hours and minutes in a panel beside it.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeBound",
          "kind": "type",
          "selector": null,
          "detail": "| PctTimeOfDay | undefined | LogicFn<TValue, PctTimeOfDay | undefined, TPathKind>",
          "description": "A bound pctTimeBounds takes: a time, or a function of the field's context that returns one — the two shapes Angular's own min() takes. One that is not a time, undefined among them, is no bound, as it is on the field's input.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeBoundError",
          "kind": "type",
          "selector": null,
          "detail": "| { readonly kind: 'minTime'",
          "description": "What pctTimeBounds reports: minTime for a time before min, maxTime for one after max — and both at once for a time in the gap of a window across midnight, which is the native element's reading, \"under and over\" (0086, A12). Each carries the bound it broke, as the bound was written.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTimeBoundsConfig",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The bounds of pctTimeBounds, and the sentence its errors carry — one for both, or one written from the error, which holds the bound.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctTimeBounds",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Bounds a time field in a signal form: what Angular's min() and max() do for a number, for a PctTimeOfDay — which they cannot take, and which the compiler refuses as an attribute beside [formField] (NG8022). The bounds reach the field's min / max, so the panel's walk stops at them, and the form grades the value: a time outside is minTime or maxTime, and a time in the gap of a window across midnight — min later than max, 22:00 to 06:00 — is both, as the native element reads it (0086, A12).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        }
      ],
      "parts": [
        {
          "name": "control",
          "description": "the field's typed input"
        },
        {
          "name": "error",
          "description": "the message when the value is invalid"
        },
        {
          "name": "hint",
          "description": "the hint under the input"
        },
        {
          "name": "label",
          "description": "the label of the field's typed input"
        },
        {
          "name": "panel",
          "description": "the dialog the columns stand in when the field opens them"
        },
        {
          "name": "toggle",
          "description": "the button that opens the columns"
        },
        {
          "name": "warning",
          "description": "the message when the value is allowed and suspect — after the error, before the hint (0087)"
        },
        {
          "name": "column",
          "description": "one listbox — one field of the time, one tab stop, and the element that scrolls"
        },
        {
          "name": "option",
          "description": "one row of a column; data-pct-chosen, data-pct-active and data-pct-disabled on it"
        }
      ],
      "tokens": [
        {
          "name": "--pct-time-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the time field."
        },
        {
          "name": "--pct-time-bg-disabled",
          "type": "color",
          "ref": "{pct.surface-disabled}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the time field while disabled."
        },
        {
          "name": "--pct-time-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the time field."
        },
        {
          "name": "--pct-time-border-disabled",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge colour of the time field while disabled."
        },
        {
          "name": "--pct-time-border-focus",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge colour of the time field when focused."
        },
        {
          "name": "--pct-time-border-hover",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Edge colour of the time field on hover."
        },
        {
          "name": "--pct-time-border-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Edge colour of the time field when invalid."
        },
        {
          "name": "--pct-time-border-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Edge colour of the time field under a warning (0087)."
        },
        {
          "name": "--pct-time-column-height",
          "type": "dimension",
          "ref": null,
          "light": "224px",
          "dark": "224px",
          "description": "Height of a column."
        },
        {
          "name": "--pct-time-column-min-width",
          "type": "dimension",
          "ref": null,
          "light": "56px",
          "dark": "56px",
          "description": "Minimum width of a column — a day-period word wider than two digits widens its own column."
        },
        {
          "name": "--pct-time-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the time field."
        },
        {
          "name": "--pct-time-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of the time field while disabled."
        },
        {
          "name": "--pct-time-fg-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Text colour of the time field when invalid."
        },
        {
          "name": "--pct-time-fg-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Text colour of the warning line (0087)."
        },
        {
          "name": "--pct-time-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of the time field."
        },
        {
          "name": "--pct-time-font-size-lg",
          "type": "dimension",
          "ref": "{pct.font.size.lg}",
          "light": "16px",
          "dark": "16px",
          "description": "Font size of the time field at size lg."
        },
        {
          "name": "--pct-time-font-size-sm",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of the time field at size sm."
        },
        {
          "name": "--pct-time-hint-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the hint."
        },
        {
          "name": "--pct-time-icon-size",
          "type": "dimension",
          "ref": null,
          "light": "16px",
          "dark": "16px",
          "description": "Size of the icon."
        },
        {
          "name": "--pct-time-label-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the label."
        },
        {
          "name": "--pct-time-option-bg-hover",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of a row on hover."
        },
        {
          "name": "--pct-time-option-bg-selected",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Background of a row when selected."
        },
        {
          "name": "--pct-time-option-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of a row while disabled."
        },
        {
          "name": "--pct-time-option-fg-selected",
          "type": "color",
          "ref": "{pct.on-primary}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Text colour of a row when selected."
        },
        {
          "name": "--pct-time-option-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of a row."
        },
        {
          "name": "--pct-time-option-font-size-lg",
          "type": "dimension",
          "ref": "{pct.font.size.lg}",
          "light": "16px",
          "dark": "16px",
          "description": "Font size of a row at size lg."
        },
        {
          "name": "--pct-time-option-font-size-sm",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of a row at size sm."
        },
        {
          "name": "--pct-time-option-font-weight-selected",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of a row when selected."
        },
        {
          "name": "--pct-time-option-height",
          "type": "dimension",
          "ref": null,
          "light": "32px",
          "dark": "32px",
          "description": "Height of a row."
        },
        {
          "name": "--pct-time-option-height-lg",
          "type": "dimension",
          "ref": null,
          "light": "36px",
          "dark": "36px",
          "description": "Height of a row at size lg."
        },
        {
          "name": "--pct-time-option-height-sm",
          "type": "dimension",
          "ref": null,
          "light": "28px",
          "dark": "28px",
          "description": "Height of a row at size sm."
        },
        {
          "name": "--pct-time-option-padding-x",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of a row."
        },
        {
          "name": "--pct-time-option-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of a row."
        },
        {
          "name": "--pct-time-padding-x",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of the time field."
        },
        {
          "name": "--pct-time-padding-x-lg",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of the time field at size lg."
        },
        {
          "name": "--pct-time-padding-x-sm",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of the time field at size sm."
        },
        {
          "name": "--pct-time-padding-y",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Vertical padding of the time field."
        },
        {
          "name": "--pct-time-padding-y-lg",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Vertical padding of the time field at size lg."
        },
        {
          "name": "--pct-time-padding-y-sm",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Vertical padding of the time field at size sm."
        },
        {
          "name": "--pct-time-panel-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the panel."
        },
        {
          "name": "--pct-time-panel-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the panel."
        },
        {
          "name": "--pct-time-panel-gap",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Gap between the columns of the panel."
        },
        {
          "name": "--pct-time-panel-padding-x",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of the panel."
        },
        {
          "name": "--pct-time-panel-padding-y",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Vertical padding of the panel."
        },
        {
          "name": "--pct-time-panel-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the panel."
        },
        {
          "name": "--pct-time-panel-shadow",
          "type": "shadow",
          "ref": "{pct.shadow-panel}",
          "light": "0 8px 24px rgba(15, 23, 42, 0.14)",
          "dark": "0 8px 24px rgba(0, 0, 0, 0.55)",
          "description": "Shadow of the panel."
        },
        {
          "name": "--pct-time-placeholder-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the placeholder."
        },
        {
          "name": "--pct-time-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the time field."
        },
        {
          "name": "--pct-time-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Touch-target floor of the time field."
        },
        {
          "name": "--pct-time-toggle-bg-hover",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the clock toggle on hover."
        },
        {
          "name": "--pct-time-toggle-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the clock toggle."
        },
        {
          "name": "--pct-time-toggle-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the clock toggle."
        },
        {
          "name": "--pct-time-toggle-size",
          "type": "dimension",
          "ref": null,
          "light": "20px",
          "dark": "20px",
          "description": "Size of the clock toggle."
        }
      ],
      "texts": [
        "fieldWarning",
        "timeHourLetter",
        "timeHours",
        "timeMalformed",
        "timeMinuteLetter",
        "timeMinutes",
        "timeOpen",
        "timePeriod",
        "timeSecondLetter",
        "timeSeconds"
      ],
      "keyboard": "| key                     | effect                                                                                                                                                                                                                                                                                                      | test                                            |\n| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |\n| `ArrowDown` / `ArrowUp` | the next / previous row the bounds allow, coming round at the ends                                                                                                                                                                                                                                          | `libs/components/time/src/time-columns.spec.ts` |\n| `Home` / `End`          | the first / last row the bounds allow                                                                                                                                                                                                                                                                       | `libs/components/time/src/time-columns.spec.ts` |\n| digits                  | the row that writes the number typed — `1` then `3` is thirteen on a twenty-four-hour clock, `9` is `09`; where no row writes it, the first that begins with it (`4` in quarter hours is `45`); a number no row holds leaves the column where it is, and a key after a run that named nothing starts afresh | `libs/components/time/src/time-columns.spec.ts` |\n| a letter                | the half of the day the letters typed so far begin — `a` is `AM`, `p` then `m` is `PM`                                                                                                                                                                                                                      | `libs/components/time/src/time-columns.spec.ts` |\n| `Enter` / `Space`       | takes the time the columns stand on, and says it was picked                                                                                                                                                                                                                                                 | `libs/components/time/src/time-columns.spec.ts` |\n| `Tab`                   | the next column — one stop each                                                                                                                                                                                                                                                                             | `apps/sandbox-e2e/src/time.spec.ts`             |\n\nEvery key above that moves the walk moves the VALUE, `Enter` and `Space` included: a column is\na field. A key that moves nothing writes nothing — an arrow at the only row the bounds leave, a\ndigit or a letter no row answers — and a modified key or one the column has no use for —\n`ArrowLeft`, `Tab`, `Escape` — is left to whoever owns it, which in a panel is the field.",
      "decisions": [
        {
          "id": "0086",
          "title": "A time of day is a wall clock, and the field that takes one is text the application formats"
        },
        {
          "id": "0032",
          "title": "A menu moves focus, a listbox points at it"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 77.97,
          "killed": 308,
          "survived": 84,
          "mutants": 395,
          "files": 1
        },
        "e2e": {
          "cases": 23,
          "spec": "apps/sandbox-e2e/src/time.spec.ts"
        },
        "pairs": 0,
        "baselines": 0,
        "cost": {
          "elements": 79,
          "depth": 3,
          "listeners": 78,
          "renders": 1,
          "micros": 31538,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "dialog",
      "classes": [
        "PctDialog"
      ],
      "role": "a modal dialog",
      "category": "Overlays",
      "status": "released",
      "entrypoint": "@pacit/components/dialog",
      "selectors": [
        "pct-dialog"
      ],
      "harnesses": [
        "PctDialogHarness"
      ],
      "docs": "/components/dialog",
      "summary": "A window over the page: what is behind it waits, focus stays inside, Escape closes it.",
      "usage": {
        "code": "<pct-dialog heading=\"Project settings\" [(open)]=\"open\">\n  <p>Everything outside this panel is inert while it stands.</p>\n</pct-dialog>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "guards",
          "title": "A dialog that insists",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctButton } from '@pacit/components/button';\nimport { PctDialog, PctDialogCloseReason } from '@pacit/components/dialog';\n\n/**\n * A dialog that insists\n *\n * `closeOnBackdrop` and `closeOnEscape` are on by default, because a modal that traps is a\n * modal people hate. Turn them off for the one case that earns it — an unsaved form — and\n * `(closed)` still tells you how the reader got out.\n */\n@Component({\n  selector: 'demo-dialog-guards',\n  imports: [PctButton, PctDialog],\n  styles: ':host { display: grid; gap: 12px; justify-items: start; }',\n  template: `\n    <button pctButton (click)=\"open.set(true)\">Discard changes?</button>\n    <pct-dialog\n      heading=\"Discard changes?\"\n      [(open)]=\"open\"\n      [closeOnBackdrop]=\"false\"\n      [closeOnEscape]=\"false\"\n      [closeButton]=\"false\"\n      (closed)=\"reason.set($event)\"\n    >\n      <p>Three edits go with it. There is no undo after this.</p>\n      <div style=\"display: flex; gap: 8px; justify-content: flex-end\">\n        <button pctButton variant=\"outline\" (click)=\"open.set(false)\">\n          Keep editing\n        </button>\n        <button pctButton (click)=\"open.set(false)\">Discard</button>\n      </div>\n    </pct-dialog>\n    <p>Closed by: {{ reason() ?? 'not opened yet' }}</p>\n  `,\n})\nexport class DialogGuardsDemo {\n  readonly open = signal(false);\n  readonly reason = signal<PctDialogCloseReason | null>(null);\n}"
        }
      ],
      "api": [
        {
          "name": "PctDialog",
          "kind": "component",
          "selector": "pct-dialog",
          "description": "A modal dialog: a panel that takes focus, and a page that stops answering while it is up.",
          "members": [
            {
              "name": "open",
              "kind": "model",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the dialog is up. A model, because both directions are ordinary: an application opens it, and the dialog closes itself on Escape, on the backdrop and on its own button.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "inline",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the panel is drawn where the consumer wrote the tag instead of in an overlay. The same <ng-template> either way — the same box, the same parts, the same stylesheet — so the two modes cannot drift apart the way a second hand-written panel would.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "heading",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The visible title, rendered as the panel's heading and used as its accessible name. A dialog with no name is a dialog a screen reader announces as \"dialog\" and nothing else, so an empty one is reported in dev mode rather than passed over.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name of a dialog with no visible heading — an INPUT and not an attribute on the tag, for the select's reason: role=\"dialog\" sits on the panel inside the overlay, and the host carries no role at all (req-a11y-built-in).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabelledby",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "As ariaLabel, for a name that already stands somewhere inside the dialog's content.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "closeOnEscape",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "true",
              "description": "Whether Escape closes it.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "closeOnBackdrop",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "true",
              "description": "Whether a press on the surface around the panel closes it.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "closeButton",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "true",
              "description": "Whether the panel draws a close button of its own in the header.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "closed",
              "kind": "output",
              "required": false,
              "type": "PctDialogCloseReason",
              "default": null,
              "description": "Why it closed. The reason is what an application acts on — escape and backdrop are a withdrawal, the content's own buttons are an answer — and open carries none of it.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-dialog",
              "note": ""
            },
            {
              "attribute": "style",
              "kind": "static",
              "bound": "display: contents",
              "note": "The host draws nothing of its own. As an overlay it renders nothing at all and stays in the tree because it is what the panel reads its severed properties from; <code>inline</code> it holds the panel, and <code>display: contents</code> is then what puts that panel exactly where the consumer's markup put the tag."
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctAutofocus",
          "kind": "directive",
          "selector": "[pctAutofocus]",
          "detail": null,
          "description": "Which element inside a dialog takes focus when it opens.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDialog",
          "kind": "component",
          "selector": "pct-dialog",
          "detail": null,
          "description": "A modal dialog: a panel that takes focus, and a page that stops answering while it is up.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDialogCloseReason",
          "kind": "type",
          "selector": null,
          "detail": "'escape' | 'backdrop' | 'close' | 'api'",
          "description": "Why a dialog closed. A boolean says *that* it closed and nothing else, and the two questions an application asks a modal — \"did the user agree\" and \"did the user get out of it\" — are both answered by this and by nothing in open.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "backdrop",
          "description": "the scrim over the page — an overlay dialog only"
        },
        {
          "name": "close",
          "description": "the close button"
        },
        {
          "name": "content",
          "description": "the projected body"
        },
        {
          "name": "header",
          "description": "the row holding the heading and the close button"
        },
        {
          "name": "heading",
          "description": "the title the dialog is named by"
        },
        {
          "name": "panel",
          "description": "the box the dialog draws"
        }
      ],
      "tokens": [
        {
          "name": "--pct-dialog-backdrop-alpha",
          "type": "dimension",
          "ref": null,
          "light": "40%",
          "dark": "40%",
          "description": "Opacity of the backdrop."
        },
        {
          "name": "--pct-dialog-backdrop-bg",
          "type": "color",
          "ref": "{pct.scrim}",
          "light": "#0f172a",
          "dark": "#000000",
          "description": "Background of the backdrop."
        },
        {
          "name": "--pct-dialog-backdrop-blur",
          "type": "dimension",
          "ref": null,
          "light": "3px",
          "dark": "3px",
          "description": "Blur of the page behind the backdrop."
        },
        {
          "name": "--pct-dialog-close-bg-hover",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the close button on hover."
        },
        {
          "name": "--pct-dialog-close-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the close button."
        },
        {
          "name": "--pct-dialog-close-fg-hover",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the close button on hover."
        },
        {
          "name": "--pct-dialog-close-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the close button."
        },
        {
          "name": "--pct-dialog-close-size",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Size of the close button."
        },
        {
          "name": "--pct-dialog-heading-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the heading."
        },
        {
          "name": "--pct-dialog-heading-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.lg}",
          "light": "16px",
          "dark": "16px",
          "description": "Font size of the heading."
        },
        {
          "name": "--pct-dialog-heading-font-weight",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of the heading."
        },
        {
          "name": "--pct-dialog-panel-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the panel."
        },
        {
          "name": "--pct-dialog-panel-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the panel."
        },
        {
          "name": "--pct-dialog-panel-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the panel."
        },
        {
          "name": "--pct-dialog-panel-gap",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Gap between the panel's heading, body and actions."
        },
        {
          "name": "--pct-dialog-panel-max-width",
          "type": "dimension",
          "ref": null,
          "light": "480px",
          "dark": "480px",
          "description": "Maximum width of the panel."
        },
        {
          "name": "--pct-dialog-panel-padding-x",
          "type": "dimension",
          "ref": "{pct.space.5}",
          "light": "16px",
          "dark": "16px",
          "description": "Horizontal padding of the panel."
        },
        {
          "name": "--pct-dialog-panel-padding-y",
          "type": "dimension",
          "ref": "{pct.space.5}",
          "light": "16px",
          "dark": "16px",
          "description": "Vertical padding of the panel."
        },
        {
          "name": "--pct-dialog-panel-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the panel."
        },
        {
          "name": "--pct-dialog-panel-shadow",
          "type": "shadow",
          "ref": "{pct.shadow-panel}",
          "light": "0 8px 24px rgba(15, 23, 42, 0.14)",
          "dark": "0 8px 24px rgba(0, 0, 0, 0.55)",
          "description": "Shadow of the panel."
        }
      ],
      "texts": [
        "dialogClose"
      ],
      "keyboard": "| key         | effect                                                   | test                                  |\n| ----------- | -------------------------------------------------------- | ------------------------------------- |\n| `Escape`    | close, with the reason `escape`; off via `closeOnEscape` | `apps/sandbox-e2e/src/dialog.spec.ts` |\n| `Tab`       | walks the panel and never leaves it                      | `apps/sandbox-e2e/src/dialog.spec.ts` |\n| `Shift+Tab` | the same walk backwards                                  | `apps/sandbox-e2e/src/dialog.spec.ts` |\n\nEverything else belongs to the controls inside the content. **Escape is not the dialog's alone:\na panel opened within it is a later overlay in the stack and answers the key first** — the\nselect's list closes, the dialog stays (`apps/sandbox-e2e/src/dialog.spec.ts`, \"Escape closes\nthe list first and the dialog second\").\n\n**The whole table is the overlay's.** An inline dialog is in no stack, so Escape passes it by and\n`closeOnEscape` governs nothing; Tab walks in and straight out again, because the panel is text in\nthe page and there is no trap to hold it. Its ways out are the close button and whatever the\ncontent holds (`libs/components/dialog/src/dialog.spec.ts`).",
      "decisions": [
        {
          "id": "0066",
          "title": "A panel is a layer or a region, and the consumer says which"
        },
        {
          "id": "0029",
          "title": "A modal is an overlay, not a <dialog> — the platform's modal is not composable with ours"
        },
        {
          "id": "0024",
          "title": "The overlay layer carries what an overlay severs, and borrows the closing stack"
        },
        {
          "id": "0025",
          "title": "A panel says whether it takes focus, and the rest of the focus layer waits for a consumer"
        },
        {
          "id": "0013",
          "title": "No headless core / skin split"
        },
        {
          "id": "0047",
          "title": "A drawer is a region of the page, not a layer over it"
        },
        {
          "id": "0007",
          "title": "Configuration apart from texts"
        },
        {
          "id": "0028",
          "title": "An icon set is a component, and an icon's box is the contract"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 79.03,
          "killed": 98,
          "survived": 26,
          "mutants": 124,
          "files": 1
        },
        "e2e": {
          "cases": 15,
          "spec": "apps/sandbox-e2e/src/dialog.spec.ts"
        },
        "pairs": 5,
        "baselines": 2,
        "cost": {
          "elements": 3,
          "depth": 2,
          "listeners": 3,
          "renders": 1,
          "micros": 4440,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "drawer",
      "classes": [
        "PctDrawer"
      ],
      "role": "a panel docked to an edge of the window that the page works around",
      "category": "Overlays",
      "status": "released",
      "entrypoint": "@pacit/components/drawer",
      "selectors": [
        "pct-drawer",
        "button[pctDrawerTrigger]"
      ],
      "harnesses": [
        "PctDrawerHarness",
        "PctDrawerTriggerHarness"
      ],
      "docs": "/components/drawer",
      "summary": "A panel docked to an edge of the window that the page makes room for instead of disappearing under.",
      "usage": {
        "code": "<button pctButton [pctDrawerTrigger]=\"nav\">Sections</button> <pct-drawer #nav heading=\"Sections\">…</pct-drawer>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "side",
          "title": "From the other side",
          "code": "import { Component } from '@angular/core';\nimport { PctButton } from '@pacit/components/button';\nimport { PctDrawer, PctDrawerTrigger } from '@pacit/components/drawer';\n\n/**\n * From the other side\n *\n * `side=\"end\"` slides the panel in from the inline end — the right edge in a left-to-right\n * page, the left edge under `dir=\"rtl\"`, with nothing to rewrite.\n */\n@Component({\n  selector: 'demo-drawer-side',\n  imports: [PctButton, PctDrawer, PctDrawerTrigger],\n  styles: ':host { display: flex; gap: 8px; flex-wrap: wrap; }',\n  template: `\n    <button pctButton variant=\"outline\" [pctDrawerTrigger]=\"cart\">\n      Cart (3)\n    </button>\n    <pct-drawer #cart side=\"end\" heading=\"Your cart\">\n      <ul style=\"margin: 0; padding-inline-start: 1.2rem\">\n        <li>Design tokens, 1</li>\n        <li>Components, 33</li>\n        <li>Schematics, 2</li>\n      </ul>\n    </pct-drawer>\n  `,\n})\nexport class DrawerSideDemo {}"
        }
      ],
      "api": [
        {
          "name": "PctDrawer",
          "kind": "component",
          "selector": "pct-drawer",
          "description": "A drawer: a panel docked to an edge of the window that the page goes on working around.",
          "members": [
            {
              "name": "open",
              "kind": "model",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the drawer stands open. A model, because both directions are ordinary: an application opens it, and the drawer closes itself on Escape, on its own cross and on a second press of the trigger.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "side",
              "kind": "input",
              "required": false,
              "type": "PctDrawerSide",
              "default": "'start'",
              "description": "Which edge it is docked to. See PctDrawerSide for why it is logical.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "heading",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The visible title, drawn at the top of the panel and used as its accessible name. A region with no name is not a landmark at all — a screen reader lists it as \"region\" and the user has to read the panel to find out what it is — so an unnamed one is reported in dev mode.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name of a drawer with no visible heading.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabelledby",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "As ariaLabel, for a name that already stands somewhere inside the content.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "closeButton",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "true",
              "description": "Whether the panel draws a cross of its own in the header.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "closeOnEscape",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "true",
              "description": "Whether Escape closes it — and only an Escape pressed inside the panel, because the page behind is live. A non-modal panel that answered every Escape in the document would be taking the key away from whatever the user was really doing.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "closed",
              "kind": "output",
              "required": false,
              "type": "PctDrawerCloseReason",
              "default": null,
              "description": "Why it closed.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-drawer",
              "note": ""
            },
            {
              "attribute": "role",
              "kind": "static",
              "bound": "region",
              "note": "The host IS the panel, so the role, the name and the state all sit on the tag the consumer typed — the opposite of the dialog, whose role travels to an overlay and whose name therefore has to be an input (<code>req-a11y-built-in</code>). A named <code>region</code> is a landmark: a screen reader reaches a parked panel by its name without walking the page to it."
            },
            {
              "attribute": "id",
              "kind": "property",
              "bound": "panelId",
              "note": ""
            },
            {
              "attribute": "aria-label",
              "kind": "attribute",
              "bound": "ariaLabel() || null",
              "note": ""
            },
            {
              "attribute": "data-pct-side",
              "kind": "attribute",
              "bound": "side()",
              "note": ""
            },
            {
              "attribute": "data-pct-open",
              "kind": "attribute",
              "bound": "open() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "hidden",
              "kind": "attribute",
              "bound": "present() ? null : \"until-found\"",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctDrawer",
          "kind": "component",
          "selector": "pct-drawer",
          "detail": null,
          "description": "A drawer: a panel docked to an edge of the window that the page goes on working around.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDrawerTrigger",
          "kind": "directive",
          "selector": "button[pctDrawerTrigger]",
          "detail": null,
          "description": "The control that opens a drawer: it toggles the panel and says so about itself.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDrawerSide",
          "kind": "type",
          "selector": null,
          "detail": "'start' | 'end' | 'top' | 'bottom'",
          "description": "Which edge of the viewport the drawer is docked to.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctDrawerCloseReason",
          "kind": "type",
          "selector": null,
          "detail": "'escape' | 'close' | 'trigger' | 'api'",
          "description": "Why the drawer closed.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "close",
          "description": "the close button"
        },
        {
          "name": "content",
          "description": "the projected body"
        },
        {
          "name": "header",
          "description": "the row holding the heading and the close button"
        },
        {
          "name": "heading",
          "description": "the title the drawer is named by"
        }
      ],
      "tokens": [
        {
          "name": "--pct-drawer-close-bg-hover",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the close button on hover."
        },
        {
          "name": "--pct-drawer-close-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the close button."
        },
        {
          "name": "--pct-drawer-close-fg-hover",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the close button on hover."
        },
        {
          "name": "--pct-drawer-close-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the close button."
        },
        {
          "name": "--pct-drawer-close-size",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Size of the close button."
        },
        {
          "name": "--pct-drawer-heading-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the heading."
        },
        {
          "name": "--pct-drawer-heading-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.lg}",
          "light": "16px",
          "dark": "16px",
          "description": "Font size of the heading."
        },
        {
          "name": "--pct-drawer-heading-font-weight",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of the heading."
        },
        {
          "name": "--pct-drawer-panel-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the panel."
        },
        {
          "name": "--pct-drawer-panel-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the panel."
        },
        {
          "name": "--pct-drawer-panel-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the panel."
        },
        {
          "name": "--pct-drawer-panel-gap",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Gap between the panel's heading and its content."
        },
        {
          "name": "--pct-drawer-panel-height",
          "type": "dimension",
          "ref": null,
          "light": "320px",
          "dark": "320px",
          "description": "Height of the panel."
        },
        {
          "name": "--pct-drawer-panel-padding-x",
          "type": "dimension",
          "ref": "{pct.space.5}",
          "light": "16px",
          "dark": "16px",
          "description": "Horizontal padding of the panel."
        },
        {
          "name": "--pct-drawer-panel-padding-y",
          "type": "dimension",
          "ref": "{pct.space.5}",
          "light": "16px",
          "dark": "16px",
          "description": "Vertical padding of the panel."
        },
        {
          "name": "--pct-drawer-panel-shadow",
          "type": "shadow",
          "ref": "{pct.shadow-panel}",
          "light": "0 8px 24px rgba(15, 23, 42, 0.14)",
          "dark": "0 8px 24px rgba(0, 0, 0, 0.55)",
          "description": "Shadow of the panel."
        },
        {
          "name": "--pct-drawer-panel-width",
          "type": "dimension",
          "ref": null,
          "light": "320px",
          "dark": "320px",
          "description": "Width of the panel."
        },
        {
          "name": "--pct-drawer-z-index",
          "type": "number",
          "ref": null,
          "light": "900",
          "dark": "900",
          "description": "Stacking level of the drawer."
        }
      ],
      "texts": [
        "drawerClose"
      ],
      "keyboard": "| key                    | effect                                                                   | test                                  |\n| ---------------------- | ------------------------------------------------------------------------ | ------------------------------------- |\n| `Tab`                  | walks in and out of the panel in document order — nothing is spliced     | `apps/sandbox-e2e/src/drawer.spec.ts` |\n| `Enter` / `Space`      | on the trigger: opens or shuts it — the **platform's**, off a `<button>` | `apps/sandbox-e2e/src/drawer.spec.ts` |\n| `Escape` inside        | shuts it, and gives the keyboard back to the control that opened it      | `apps/sandbox-e2e/src/drawer.spec.ts` |\n| `Escape` anywhere else | nothing — the key stays the page's                                       | `apps/sandbox-e2e/src/drawer.spec.ts` |\n\n**There is no focus trap and no arrow-key walk.** Both would make this a modal, which it is not:\na panel the user is expected to leave and come back to has to let them leave.",
      "decisions": [
        {
          "id": "0047",
          "title": "A drawer is a region of the page, not a layer over it"
        },
        {
          "id": "0031",
          "title": "A panel's tab order belongs to its trigger"
        },
        {
          "id": "0045",
          "title": "A panel nobody chose is still text in the document"
        },
        {
          "id": "0046",
          "title": "A disclosure is the platform's, and so is the group it belongs to"
        },
        {
          "id": "0029",
          "title": "A modal is an overlay, not a <dialog> — the platform's modal is not composable with ours"
        },
        {
          "id": "0011",
          "title": "Icons through a template and PCT_ICONS"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 79.19,
          "killed": 118,
          "survived": 30,
          "mutants": 149,
          "files": 1
        },
        "e2e": {
          "cases": 13,
          "spec": "apps/sandbox-e2e/src/drawer.spec.ts"
        },
        "pairs": 5,
        "baselines": 1,
        "cost": {
          "elements": 14,
          "depth": 6,
          "listeners": 4,
          "renders": 1,
          "micros": 6690,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "popover",
      "classes": [
        "PctPopover"
      ],
      "role": "a panel of content on a live page",
      "category": "Overlays",
      "status": "released",
      "entrypoint": "@pacit/components/popover",
      "selectors": [
        "pct-popover"
      ],
      "harnesses": [
        "PctPopoverHarness",
        "PctPopoverTriggerHarness"
      ],
      "docs": "/components/popover",
      "summary": "A panel of content anchored to a control, with the page behind it still answering.",
      "usage": {
        "code": "<button pctButton [pctPopoverTrigger]=\"filters\">Filters</button> <pct-popover #filters heading=\"Filters\">…</pct-popover>\n\n<!-- the same panel drawn where it stands: no layer, no anchor, and there from the server -->\n<pct-popover inline [open]=\"true\" heading=\"Filters\">…</pct-popover>\n\n<!-- one panel with two lives — a column on a wide page, a layer on a narrow one -->\n<button pctButton [pctPopoverTrigger]=\"filters\">Filters</button>\n<pct-popover #filters [inline]=\"wide()\" [(open)]=\"filtersOpen\" heading=\"Filters\">…</pct-popover>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "placement",
          "title": "Placement",
          "code": "import { Component } from '@angular/core';\nimport { PctButton } from '@pacit/components/button';\nimport { PctPopover, PctPopoverTrigger } from '@pacit/components/popover';\n\n/**\n * Placement\n *\n * Ask for a side with `placement`; the panel takes it when it fits and flips to the\n * opposite side when the viewport says no — a wish, not an order.\n */\n@Component({\n  selector: 'demo-popover-placement',\n  imports: [PctButton, PctPopover, PctPopoverTrigger],\n  styles: ':host { display: flex; gap: 8px; flex-wrap: wrap; }',\n  template: `\n    <button pctButton variant=\"outline\" [pctPopoverTrigger]=\"above\">\n      Above\n    </button>\n    <pct-popover #above placement=\"top\" heading=\"Above the trigger\">\n      <p>Unless there is no room up here.</p>\n    </pct-popover>\n    <button pctButton variant=\"outline\" [pctPopoverTrigger]=\"beside\">\n      Beside\n    </button>\n    <pct-popover #beside placement=\"end\" heading=\"Beside the trigger\">\n      <p>And on the other side under rtl.</p>\n    </pct-popover>\n  `,\n})\nexport class PopoverPlacementDemo {}"
        }
      ],
      "api": [
        {
          "name": "PctPopover",
          "kind": "component",
          "selector": "pct-popover",
          "description": "A popover: a panel of content hanging off the control that opened it, with focus inside it and the rest of the page still answering.",
          "members": [
            {
              "name": "open",
              "kind": "model",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether the panel is up. A model, because both directions are ordinary: an application opens it, and the popover closes itself on Escape, on a press outside and on the trigger.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "heading",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The visible title, rendered as the panel's heading and used as its accessible name. A panel with no name is announced as \"dialog\" and nothing else, so an empty one is reported in dev mode rather than passed over.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name of a popover with no visible heading — an INPUT and not an attribute on the tag, for the dialog's reason: the role sits on the panel inside the overlay, and the host carries none at all (req-a11y-built-in).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabelledby",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "As ariaLabel, for a name that already stands somewhere inside the content.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "placement",
              "kind": "input",
              "required": false,
              "type": "PctPlacement",
              "default": "'bottom'",
              "description": "Which side of the trigger it opens on — logical, so end is the right in an English page and the left in an Arabic one. The window has the last word: a side with no room for the panel falls back to the one across the trigger (pctPlacementPositions). It says nothing inline: a panel drawn in the page is placed by the page.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "inline",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Draws the panel in the host, where the consumer wrote the component, instead of in an overlay over the page. The same template, the same classes, the same parts and the same stylesheet — a second panel written out for this case is the defect the input exists to avoid.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "closed",
              "kind": "output",
              "required": false,
              "type": "PctPopoverCloseReason",
              "default": null,
              "description": "Why it closed. See PctPopoverCloseReason.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-popover",
              "note": ""
            },
            {
              "attribute": "style",
              "kind": "static",
              "bound": "display: contents",
              "note": "The host draws nothing of its own in either mode: with an overlay everything it draws lives outside the tree, and it stays here because it is where the panel's severed properties are read from. <code>display: contents</code> is also what makes the inline panel land in the consumer's own layout — the host box is not there to be a wrapper between the page's grid and the panel it wrote into it."
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctPopover",
          "kind": "component",
          "selector": "pct-popover",
          "detail": null,
          "description": "A popover: a panel of content hanging off the control that opened it, with focus inside it and the rest of the page still answering.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctPopoverTrigger",
          "kind": "directive",
          "selector": "[pctPopoverTrigger]",
          "detail": null,
          "description": "The control a popover hangs off: it toggles the panel and says so about itself.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctPopoverCloseReason",
          "kind": "type",
          "selector": null,
          "detail": "'trigger' | 'escape' | 'outside' | 'away' | 'api'",
          "description": "Why a popover closed. open says *that* it is shut and nothing else, and the question an application asks about a panel it did not close itself — did the user answer, or did they walk away from it — is answered by this and by nothing in the boolean.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "content",
          "description": "the projected body"
        },
        {
          "name": "heading",
          "description": "the title the panel is named by"
        },
        {
          "name": "panel",
          "description": "the floating box"
        }
      ],
      "tokens": [
        {
          "name": "--pct-popover-heading-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the heading."
        },
        {
          "name": "--pct-popover-heading-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of the heading."
        },
        {
          "name": "--pct-popover-heading-font-weight",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of the heading."
        },
        {
          "name": "--pct-popover-panel-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the panel."
        },
        {
          "name": "--pct-popover-panel-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of the panel."
        },
        {
          "name": "--pct-popover-panel-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the panel."
        },
        {
          "name": "--pct-popover-panel-gap",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Gap between the panel's heading and its content."
        },
        {
          "name": "--pct-popover-panel-max-width",
          "type": "dimension",
          "ref": null,
          "light": "320px",
          "dark": "320px",
          "description": "Maximum width of the panel."
        },
        {
          "name": "--pct-popover-panel-padding-x",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of the panel."
        },
        {
          "name": "--pct-popover-panel-padding-y",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Vertical padding of the panel."
        },
        {
          "name": "--pct-popover-panel-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the panel."
        },
        {
          "name": "--pct-popover-panel-shadow",
          "type": "shadow",
          "ref": "{pct.shadow-panel}",
          "light": "0 8px 24px rgba(15, 23, 42, 0.14)",
          "dark": "0 8px 24px rgba(0, 0, 0, 0.55)",
          "description": "Shadow of the panel."
        }
      ],
      "texts": [],
      "keyboard": "| key                            | effect                                                                                                       | test                                          |\n| ------------------------------ | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------- |\n| `Escape`                       | closes it; focus goes back to the trigger **if it was in the panel**                                         | `apps/sandbox-e2e/src/popover.spec.ts`        |\n| `Tab`                          | from the panel, into its content; from the last control, out — closing it and returning focus to the trigger | `apps/sandbox-e2e/src/popover.spec.ts`        |\n| `Shift+Tab`                    | from the first control, the same way out                                                                     | `apps/sandbox-e2e/src/popover.spec.ts`        |\n| `Enter`/`Space` on the trigger | toggles it, which is the button's own behaviour and nothing this component adds                              | `libs/components/popover/src/popover.spec.ts` |\n\nThe panel itself is not in the Tab order (`tabindex=\"-1\"`); it is focused programmatically on\nopening, so that its role and name are announced before the user is anywhere inside it.\n\n**None of the first three rows is true `inline`.** Escape and the outside press are the closing\nstack's and never reach a panel that was never attached; Tab walks through the panel and out the\nfar side, which is the whole point of drawing it in the page. Each is measured there as an\nabsence, in `libs/components/popover/src/popover.spec.ts`.",
      "decisions": [
        {
          "id": "0066",
          "title": "A panel is a layer or a region, and the consumer says which"
        },
        {
          "id": "0031",
          "title": "A panel's tab order belongs to its trigger"
        },
        {
          "id": "0029",
          "title": "A modal is an overlay, not a <dialog> — the platform's modal is not composable with ours"
        },
        {
          "id": "0024",
          "title": "The overlay layer carries what an overlay severs, and borrows the closing stack"
        },
        {
          "id": "0025",
          "title": "A panel says whether it takes focus, and the rest of the focus layer waits for a consumer"
        },
        {
          "id": "0013",
          "title": "No headless core / skin split"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 70.45,
          "killed": 155,
          "survived": 50,
          "mutants": 220,
          "files": 1
        },
        "e2e": {
          "cases": 15,
          "spec": "apps/sandbox-e2e/src/popover.spec.ts"
        },
        "pairs": 3,
        "baselines": 1,
        "cost": {
          "elements": 3,
          "depth": 2,
          "listeners": 1,
          "renders": 1,
          "micros": 4111,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "toast",
      "classes": [
        "PctToaster"
      ],
      "role": "a message on top of the page",
      "category": "Overlays",
      "status": "released",
      "entrypoint": "@pacit/components/toast",
      "selectors": [],
      "harnesses": [
        "PctToastViewportHarness"
      ],
      "docs": "/components/toast",
      "summary": "A short message on top of the page, said out loud without taking focus from anybody.",
      "usage": {
        "code": "// once, in the shell:  <pct-toast-viewport />\ninject(PctToaster).show('Draft saved.');",
        "lang": "ts"
      },
      "examples": [
        {
          "key": "action",
          "title": "A message with an answer",
          "code": "import { Component, inject, signal } from '@angular/core';\nimport { PctButton } from '@pacit/components/button';\nimport { PctToaster } from '@pacit/components/toast';\n\n/**\n * A message with an answer\n *\n * `show()` takes text, or text with an `action` — the toast then carries a button, and the\n * callback runs when the reader presses it. Undo is the pattern this exists for.\n */\n@Component({\n  selector: 'demo-toast-action',\n  imports: [PctButton],\n  styles: ':host { display: grid; gap: 12px; justify-items: start; }',\n  template: `\n    <button pctButton variant=\"outline\" (click)=\"archive()\">\n      Archive 3 messages\n    </button>\n    <p>Archived: {{ archived() }}, restored: {{ restored() }}.</p>\n  `,\n})\nexport class ToastActionDemo {\n  private readonly toaster = inject(PctToaster);\n  readonly archived = signal(0);\n  readonly restored = signal(0);\n\n  protected archive(): void {\n    this.archived.update((n) => n + 3);\n    this.toaster.show({\n      text: '3 messages archived.',\n      action: { label: 'Undo', run: () => this.restored.update((n) => n + 3) },\n    });\n  }\n}"
        }
      ],
      "api": [
        {
          "name": "PctToaster",
          "kind": "injectable",
          "selector": null,
          "description": "The library's toasts — messages that arrive on top of the page rather than in it.",
          "members": [],
          "host": []
        }
      ],
      "exports": [
        {
          "name": "PctToastAction",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The one thing a toast can offer besides going away: a label and what pressing it does.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctToastNotice",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "A message that says something happened and then goes away by itself.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctToastStanding",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "A message that stays until somebody deals with it: one that can be acted on, or one urgent enough to interrupt what a screen reader is saying.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctToastSpec",
          "kind": "type",
          "selector": null,
          "detail": "PctToastNotice | PctToastStanding",
          "description": "What {@link PctToaster.show} takes: a bare sentence, or one of the two shapes above.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctToastRef",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The handle a caller keeps — enough to take back a message that has stopped being true.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctToastState",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "A message as the viewport draws it: what is on the screen and nothing else. The clock, the action's callback and the politeness the region was opened with stay with the service — a view that cannot reach them cannot get them wrong.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctToastBlock",
          "kind": "type",
          "selector": null,
          "detail": "'start' | 'end'",
          "description": "Which edge of the block axis the stack stands at — start is the top of a Latin page.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctToastInline",
          "kind": "type",
          "selector": null,
          "detail": "'start' | 'center' | 'end'",
          "description": "Where on the inline axis: start/end mirror with the writing direction, center does not.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctToastConfig",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The application's answers to the four questions a toast cannot ask per message.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PCT_DEFAULT_TOAST_CONFIG",
          "kind": "const",
          "selector": null,
          "detail": null,
          "description": "The toast configuration a page gets without asking: bottom right, six seconds, four at a time.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PCT_TOAST_CONFIG",
          "kind": "token",
          "selector": null,
          "detail": null,
          "description": "The token the toast configuration is read from; unprovided, it falls back to the default above.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "providePctToastConfig",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Registers the toast configuration (the provideX pattern, req-api-config). It is its own token rather than four fields of PctConfig, for the reason that keeps the texts apart as well: an application that never raises a toast should not carry the answers to questions it never asks.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctToastHost",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What the viewport is given, and the whole of it. The view is created by a service rather than written into somebody's template, so the two are joined by a token instead of by an import — which is also what keeps the two files from importing each other.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PCT_TOAST_HOST",
          "kind": "token",
          "selector": null,
          "detail": null,
          "description": "The token the toaster provides itself under, so the region it creates reaches it — for the messages to draw, and to say that one was dismissed or its action pressed.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctToastViewport",
          "kind": "component",
          "selector": "pct-toast-viewport",
          "detail": null,
          "description": "The place the messages appear in — one per application, created by {@link PctToaster} and never written into anybody's template.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctToaster",
          "kind": "injectable",
          "selector": null,
          "detail": null,
          "description": "The library's toasts — messages that arrive on top of the page rather than in it.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "action",
          "description": "the button that answers it — Undo, Retry"
        },
        {
          "name": "close",
          "description": "the button that dismisses it"
        },
        {
          "name": "icon",
          "description": "the tone's mark, drawn only when a message carries a tone"
        },
        {
          "name": "item",
          "description": "one toast"
        },
        {
          "name": "message",
          "description": "its text"
        }
      ],
      "tokens": [
        {
          "name": "--pct-toast-action-fg",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Text colour of the action button."
        },
        {
          "name": "--pct-toast-action-font-weight",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of the action button."
        },
        {
          "name": "--pct-toast-action-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the action button."
        },
        {
          "name": "--pct-toast-close-bg-hover",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the close button on hover."
        },
        {
          "name": "--pct-toast-close-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the close button."
        },
        {
          "name": "--pct-toast-close-fg-hover",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the close button on hover."
        },
        {
          "name": "--pct-toast-close-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the close button."
        },
        {
          "name": "--pct-toast-close-size",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Size of the close button."
        },
        {
          "name": "--pct-toast-gap",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Gap between stacked toasts."
        },
        {
          "name": "--pct-toast-icon-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Colour of the tone's mark when a message carries no tone of its own."
        },
        {
          "name": "--pct-toast-icon-fg-danger",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Colour of the mark on a message that reports a failure."
        },
        {
          "name": "--pct-toast-icon-fg-info",
          "type": "color",
          "ref": "{pct.info}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Colour of the mark on an informational message."
        },
        {
          "name": "--pct-toast-icon-fg-success",
          "type": "color",
          "ref": "{pct.success}",
          "light": "#15803d",
          "dark": "#4ade80",
          "description": "Colour of the mark on a message that reports success."
        },
        {
          "name": "--pct-toast-icon-fg-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Colour of the mark on a message that reports a warning."
        },
        {
          "name": "--pct-toast-icon-size",
          "type": "dimension",
          "ref": null,
          "light": "16px",
          "dark": "16px",
          "description": "Size of the box the tone's mark is drawn in."
        },
        {
          "name": "--pct-toast-inset",
          "type": "dimension",
          "ref": "{pct.space.5}",
          "light": "16px",
          "dark": "16px",
          "description": "Distance of the toast from the edge of the viewport."
        },
        {
          "name": "--pct-toast-item-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of an item."
        },
        {
          "name": "--pct-toast-item-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge colour of an item."
        },
        {
          "name": "--pct-toast-item-border-danger",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Edge of a message that reports a failure."
        },
        {
          "name": "--pct-toast-item-border-info",
          "type": "color",
          "ref": "{pct.info}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge of an informational message."
        },
        {
          "name": "--pct-toast-item-border-success",
          "type": "color",
          "ref": "{pct.success}",
          "light": "#15803d",
          "dark": "#4ade80",
          "description": "Edge of a message that reports success."
        },
        {
          "name": "--pct-toast-item-border-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Edge of a message that reports a warning."
        },
        {
          "name": "--pct-toast-item-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of an item."
        },
        {
          "name": "--pct-toast-item-gap",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Gap between a toast's message and its action."
        },
        {
          "name": "--pct-toast-item-max-width",
          "type": "dimension",
          "ref": null,
          "light": "360px",
          "dark": "360px",
          "description": "Maximum width of an item."
        },
        {
          "name": "--pct-toast-item-padding-x",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of an item."
        },
        {
          "name": "--pct-toast-item-padding-y",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Vertical padding of an item."
        },
        {
          "name": "--pct-toast-item-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of an item."
        },
        {
          "name": "--pct-toast-item-shadow",
          "type": "shadow",
          "ref": "{pct.shadow-panel}",
          "light": "0 8px 24px rgba(15, 23, 42, 0.14)",
          "dark": "0 8px 24px rgba(0, 0, 0, 0.55)",
          "description": "Shadow of an item."
        },
        {
          "name": "--pct-toast-message-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of the message."
        },
        {
          "name": "--pct-toast-z-index",
          "type": "number",
          "ref": null,
          "light": "1100",
          "dark": "1100",
          "description": "Stacking level of the toast."
        }
      ],
      "texts": [
        "toastDismiss",
        "toastRegion"
      ],
      "keyboard": "| key               | effect                                                               | test                                 |\n| ----------------- | -------------------------------------------------------------------- | ------------------------------------ |\n| `Tab`             | walks into the stack, and every clock stops while it is in there     | `apps/sandbox-e2e/src/toast.spec.ts` |\n| `Enter` / `Space` | presses the action or the cross — the platform's, on two `<button>`s | `apps/sandbox-e2e/src/toast.spec.ts` |\n\nThere is no key of the library's own. A toast takes no focus when it arrives (the APG's\nalert pattern), and the stack is reached the way any other part of the page is — which is also\nits sharpest limitation, below.",
      "decisions": [
        {
          "id": "0044",
          "title": "A toast is a change in a region that was already there, and its place is the top layer"
        },
        {
          "id": "0026",
          "title": "One channel per politeness, and a message with a home announces from it"
        },
        {
          "id": "0039",
          "title": "A state the platform publishes is not ours to write"
        },
        {
          "id": "0024",
          "title": "The overlay layer carries what an overlay severs, and borrows the closing stack"
        },
        {
          "id": "0007",
          "title": "Configuration apart from texts"
        },
        {
          "id": "0028",
          "title": "An icon set is a component, and an icon's box is the contract"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 65.91,
          "killed": 116,
          "survived": 49,
          "mutants": 176,
          "files": 1
        },
        "e2e": {
          "cases": 15,
          "spec": "apps/sandbox-e2e/src/toast.spec.ts"
        },
        "pairs": 14,
        "baselines": 2,
        "cost": {
          "elements": 5,
          "depth": 2,
          "listeners": 7,
          "renders": 1,
          "micros": 3996,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "tooltip",
      "classes": [
        "PctTooltip"
      ],
      "role": "a sentence about a control",
      "category": "Overlays",
      "status": "released",
      "entrypoint": "@pacit/components/tooltip",
      "selectors": [
        "[pctTooltip]"
      ],
      "harnesses": [
        "PctTooltipHarness",
        "PctTooltipPanelHarness"
      ],
      "docs": "/components/tooltip",
      "summary": "A sentence about a control — its description, or its name when it has none of its own.",
      "usage": {
        "code": "<button pctButton pctTooltip=\"Runs every check first\">Verify</button>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "placement",
          "title": "Placement, and what the sentence is",
          "code": "import { Component } from '@angular/core';\nimport { PctButton } from '@pacit/components/button';\nimport { PctIcon, providePctIcons } from '@pacit/components/icon';\nimport { svgIcons } from '@pacit/components/svg-icon';\nimport { PctTooltip } from '@pacit/components/tooltip';\nimport { RefreshCw } from 'lucide';\n\n/**\n * Placement, and what the sentence is\n *\n * `pctTooltipPlacement` asks for a side — logical, so `end` is the right in this page and the\n * left in an Arabic one. `pctTooltipAs=\"name\"` makes the sentence the control's accessible\n * name instead of its description — for an icon-only button that has no text of its own.\n */\n@Component({\n  selector: 'demo-tooltip-placement',\n  imports: [PctButton, PctIcon, PctTooltip],\n  providers: [providePctIcons(svgIcons({ refresh: RefreshCw }))],\n  styles: ':host { display: flex; gap: 8px; flex-wrap: wrap; }',\n  template: `\n    <button\n      pctButton\n      variant=\"outline\"\n      pctTooltip=\"Opens below\"\n      pctTooltipPlacement=\"bottom\"\n    >\n      Below\n    </button>\n    <button\n      pctButton\n      variant=\"outline\"\n      pctTooltip=\"Opens beside\"\n      pctTooltipPlacement=\"end\"\n    >\n      Beside\n    </button>\n    <button\n      pctButton\n      iconOnly\n      variant=\"outline\"\n      pctTooltip=\"Refresh the list\"\n      pctTooltipAs=\"name\"\n    >\n      <pct-icon icon=\"refresh\" />\n    </button>\n  `,\n})\nexport class TooltipPlacementDemo {}"
        }
      ],
      "api": [
        {
          "name": "PctTooltip",
          "kind": "directive",
          "selector": "[pctTooltip]",
          "description": "A tooltip: a short piece of text about the control it sits on, shown on hover, on keyboard focus and on a long press.",
          "members": [
            {
              "name": "text",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The text. An empty one is a tooltip that never opens rather than an empty panel.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "placement",
              "kind": "input",
              "required": false,
              "type": "PctPlacement",
              "default": "'top'",
              "description": "Which side of the control it opens on — logical, so end is the right in an English page and the left in an Arabic one. The window has the last word: a side with no room for the panel falls back to the one across the control (pctPlacementPositions).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "as",
              "kind": "input",
              "required": false,
              "type": "PctTooltipAs",
              "default": "'description'",
              "description": "Whether the text describes the control or names it. See PctTooltipAs.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "disabled",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Switches the tooltip off without taking it out of the template — the state a control has while its own text is not worth showing. A disabled tooltip also stops naming: a name that is not shown is still a name, and one an application asked to be rid of is not.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": []
        }
      ],
      "exports": [
        {
          "name": "PctTooltipPanel",
          "kind": "component",
          "selector": "pct-tooltip",
          "detail": null,
          "description": "The panel a tooltip draws — attached to an overlay by PctTooltip, never written in a template. It is public because it is what the part inventory names (req-api-parts): the panel part a consumer styles belongs to this class.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctTooltip",
          "kind": "directive",
          "selector": "[pctTooltip]",
          "detail": null,
          "description": "A tooltip: a short piece of text about the control it sits on, shown on hover, on keyboard focus and on a long press.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctTooltipAs",
          "kind": "type",
          "selector": null,
          "detail": "'description' | 'name'",
          "description": "What the tooltip's text IS to the control it hangs on — the distinction this component exists to force, and the one a title attribute never makes.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "panel",
          "description": "the bubble with the sentence"
        }
      ],
      "tokens": [
        {
          "name": "--pct-tooltip-bg",
          "type": "color",
          "ref": "{pct.surface-inverse}",
          "light": "#1e293b",
          "dark": "#f1f5f9",
          "description": "Background of the tooltip."
        },
        {
          "name": "--pct-tooltip-fg",
          "type": "color",
          "ref": "{pct.on-surface-inverse}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Text colour of the tooltip."
        },
        {
          "name": "--pct-tooltip-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of the tooltip."
        },
        {
          "name": "--pct-tooltip-max-width",
          "type": "dimension",
          "ref": null,
          "light": "280px",
          "dark": "280px",
          "description": "Maximum width of the tooltip."
        },
        {
          "name": "--pct-tooltip-padding-x",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of the tooltip."
        },
        {
          "name": "--pct-tooltip-padding-y",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Vertical padding of the tooltip."
        },
        {
          "name": "--pct-tooltip-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the tooltip."
        },
        {
          "name": "--pct-tooltip-shadow",
          "type": "shadow",
          "ref": "{pct.shadow-panel}",
          "light": "0 8px 24px rgba(15, 23, 42, 0.14)",
          "dark": "0 8px 24px rgba(0, 0, 0, 0.55)",
          "description": "Shadow of the tooltip."
        }
      ],
      "texts": [],
      "keyboard": "| key      | effect                                                             | test                                   |\n| -------- | ------------------------------------------------------------------ | -------------------------------------- |\n| `Escape` | dismisses it, focus stays where it was (WCAG 1.4.13)               | `apps/sandbox-e2e/src/tooltip.spec.ts` |\n| `Tab`    | opens it on arrival, closes it on leaving — never enters the panel | `apps/sandbox-e2e/src/tooltip.spec.ts` |\n\nThe panel is not in the Tab order and holds nothing focusable: it is read where it stands, and\nthe control keeps focus the whole time.",
      "decisions": [
        {
          "id": "0030",
          "title": "A name is an attribute, a description is a reference"
        },
        {
          "id": "0024",
          "title": "The overlay layer carries what an overlay severs, and borrows the closing stack"
        },
        {
          "id": "0025",
          "title": "A panel says whether it takes focus, and the rest of the focus layer waits for a consumer"
        },
        {
          "id": "0013",
          "title": "No headless core / skin split"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 72.73,
          "killed": 208,
          "survived": 73,
          "mutants": 286,
          "files": 1
        },
        "e2e": {
          "cases": 13,
          "spec": "apps/sandbox-e2e/src/tooltip.spec.ts"
        },
        "pairs": 1,
        "baselines": 1,
        "cost": {
          "elements": 2,
          "depth": 2,
          "listeners": 7,
          "renders": 1,
          "micros": 2623,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "table",
      "classes": [
        "PctTable"
      ],
      "role": "the platform table, dressed element by element",
      "category": "Data & status",
      "status": "released",
      "entrypoint": "@pacit/components/table",
      "selectors": [
        "table[pctTable]",
        "caption[pctCaption]",
        "th[pctHeader]",
        "td[pctCell]",
        "pct-table-frame"
      ],
      "harnesses": [
        "PctTableHarness",
        "PctTableCaptionHarness",
        "PctTableHeaderHarness",
        "PctTableCellHarness",
        "PctTableFrameHarness"
      ],
      "docs": "/components/table",
      "summary": "A table you write yourself, in the platform's own markup, with the library's look, sortable headers and a frame that scrolls a table wider than its space.",
      "usage": {
        "code": "<table pctTable>\n  <caption pctCaption>\n    Devices\n  </caption>\n  <thead>\n    <tr>\n      <th pctHeader scope=\"col\">Device</th>\n      <th pctHeader scope=\"col\" numeric>Power</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td pctCell>Heater</td>\n      <td pctCell numeric>2.4</td>\n    </tr>\n  </tbody>\n</table>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "sorted",
          "title": "Sorted by the headers, ordered by you",
          "code": "import { Component, computed, signal } from '@angular/core';\nimport {\n  PctTable,\n  PctTableCaption,\n  PctTableCell,\n  PctTableFrame,\n  PctTableHeader,\n  PctTableSort,\n} from '@pacit/components/table';\n\nconst ROOMS = [\n  { id: 1, room: 'Kitchen', temperature: 21.5, humidity: 48 },\n  { id: 2, room: 'Bedroom', temperature: 19.0, humidity: 52 },\n  { id: 3, room: 'Office', temperature: 22.5, humidity: 41 },\n  { id: 4, room: 'Bathroom', temperature: 23.0, humidity: 67 },\n] as const;\n\ntype Column = 'room' | 'temperature' | 'humidity';\n\n/**\n * Sorted by the headers, ordered by you\n *\n * The headers with a `sortKey` propose an order into one `sort` model, and the component that\n * owns the rows puts them in it. The frame scrolls the table when the page is too narrow for\n * it, and only then is it a region with a tab stop.\n */\n@Component({\n  selector: 'demo-table-sorted',\n  imports: [\n    PctTable,\n    PctTableCaption,\n    PctTableHeader,\n    PctTableCell,\n    PctTableFrame,\n  ],\n  styles: ':host { display: block; }',\n  template: `\n    <pct-table-frame>\n      <table pctTable [(sort)]=\"sort\">\n        <caption pctCaption>\n          Rooms\n        </caption>\n        <thead>\n          <tr>\n            <th pctHeader scope=\"col\" sortKey=\"room\">Room</th>\n            <th pctHeader scope=\"col\" sortKey=\"temperature\" numeric>\n              Temperature (°C)\n            </th>\n            <th pctHeader scope=\"col\" sortKey=\"humidity\" numeric>\n              Humidity (%)\n            </th>\n          </tr>\n        </thead>\n        <tbody>\n          @for (row of rows(); track row.id) {\n            <tr>\n              <th pctHeader scope=\"row\">{{ row.room }}</th>\n              <td pctCell numeric>{{ row.temperature.toFixed(1) }}</td>\n              <td pctCell numeric>{{ row.humidity }}</td>\n            </tr>\n          }\n        </tbody>\n      </table>\n    </pct-table-frame>\n  `,\n})\nexport class TableSortedDemo {\n  protected readonly sort = signal<PctTableSort | null>(null);\n\n  protected readonly rows = computed(() => {\n    const sort = this.sort();\n    if (sort === null) return ROOMS;\n    const key = sort.key as Column;\n    const order = sort.direction === 'ascending' ? 1 : -1;\n    return [...ROOMS].sort((a, b) =>\n      key === 'room'\n        ? order * a.room.localeCompare(b.room)\n        : order * (a[key] - b[key]),\n    );\n  });\n}"
        }
      ],
      "api": [
        {
          "name": "PctTable",
          "kind": "component",
          "selector": "table[pctTable]",
          "description": "A table the consumer writes, dressed by the library: the platform's <table>, its caption, header cells and cells, each element wearing a component of its own.",
          "members": [
            {
              "name": "sort",
              "kind": "model",
              "required": false,
              "type": "PctTableSort | null",
              "default": "null",
              "description": "The column the rows stand sorted by, and which way, or null while they stand in the consumer's own order. A header with a sortKey writes it when pressed: a column not yet sorted starts ascending, and the sorted one turns the other way. Bind it two-way and order the rows from it.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-table",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctTableSort",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The column the rows stand sorted by, named by the key its header carries, and which way.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTable",
          "kind": "component",
          "selector": "table[pctTable]",
          "detail": null,
          "description": "A table the consumer writes, dressed by the library: the platform's <table>, its caption, header cells and cells, each element wearing a component of its own.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableCaption",
          "kind": "component",
          "selector": "caption[pctCaption]",
          "detail": null,
          "description": "The table's caption: its name, which a reader announces with the table, and the name of the frame around it when the frame scrolls.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableHeader",
          "kind": "component",
          "selector": "th[pctHeader]",
          "detail": null,
          "description": "A header cell. With a sortKey its content is a button that proposes sorting the rows by that column, and the header of the sorted column carries aria-sort.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableCell",
          "kind": "component",
          "selector": "td[pctCell]",
          "detail": null,
          "description": "A cell.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableFrame",
          "kind": "component",
          "selector": "pct-table-frame",
          "detail": null,
          "description": "The frame a table scrolls in when it is wider than the space it is given, or taller than a height it is given. While the table overflows, the frame is a focusable region named by the table's caption, so a keyboard can reach it and scroll it and a reader can say what it holds. While the table fits, the frame is nothing: a stop with nothing to scroll is a stop for nothing.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableVisible",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The lines in the view, [start, end) among the lines drawn, and the ids of the rows among them, in order.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctScrollAlign",
          "kind": "type",
          "selector": null,
          "detail": "'start' | 'center' | 'end' | 'nearest'",
          "description": "Where a row the table scrolls to stands in the view: at its top, its centre, its bottom, or wherever is nearest from where the view is — the default.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctScrollToOptions",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "How scrollTo scrolls.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableFeature",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What a directive on the table adds to it, given once from its constructor: a hard list, one field per feature, so the table's own directives grow it and nothing else does (0097).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctDataTable",
          "kind": "component",
          "selector": "pct-data-table",
          "detail": null,
          "description": "A table built from data: typed columns, sorting by one key or several, a quick filter, and the empty and loading states, rendered as the first layer's <table pctTable> so the two look the same (0097).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctCellContext",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What a cell template is handed.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctCellTemplate",
          "kind": "directive",
          "selector": "ng-template[pctCell]",
          "detail": null,
          "description": "A cell the consumer draws for one column, or for several that share a look.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctHeaderCellContext",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What a header template is handed.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctHeaderCellTemplate",
          "kind": "directive",
          "selector": "ng-template[pctHeaderCell]",
          "detail": null,
          "description": "What a header shows in place of the column's header text.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctEmptyContext",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What the empty-state template is handed.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctEmptyTemplate",
          "kind": "directive",
          "selector": "ng-template[pctEmpty]",
          "detail": null,
          "description": "What the table shows beside itself when it has no row to show, in place of its sentence.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctDetailsContext",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What a details template is handed.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctDetailsTemplate",
          "kind": "directive",
          "selector": "ng-template[pctDetails]",
          "detail": null,
          "description": "What stands in a row's details, below the values of the columns a narrow table hid: a chart of the row's history, a form, anything the row has room for only when it is open. The row that opens and the button that opens it are the table's (0097).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableDetails",
          "kind": "directive",
          "selector": "pct-data-table[pctTableDetails]",
          "detail": null,
          "description": "The rows whose details are open, as the application's state: [(details)] binds the list of their ids, so a row opened in one view is open in the next, and disabled takes the disclosure away.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableGroups",
          "kind": "directive",
          "selector": "pct-data-table[pctTableGroups]",
          "detail": null,
          "description": "The rows in groups by the values of one column or several, each group a body of the table headed by a row: its label, how many rows it holds, the sums of the columns that sum up, and a button that collapses it (0097, D8).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctSelectionMode",
          "kind": "type",
          "selector": null,
          "detail": "'single' | 'multiple'",
          "description": "How many rows a selection holds: one, picked with a radio, or any, with a checkbox each and one in the header over all the rows the filter left.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableSelection",
          "kind": "directive",
          "selector": "pct-data-table[pctTableSelection]",
          "detail": null,
          "description": "The rows picked, as a form control: [(value)] binds the ids of the rows picked, and [formField] makes the selection a field like any other — its errors, its warnings and its hint stand under the table, and aria-describedby on the <table> names the one shown.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableSelectionParts",
          "kind": "component",
          "selector": null,
          "detail": null,
          "description": "The selection's parts: templates the table draws in the places it keeps for them, and the handlers of the controls in them. Declared here, rendered there: the bindings are read in this component, the elements stand in the table. pctTableSelection makes it, so it has no selector: there is no tag to write.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableWindow",
          "kind": "directive",
          "selector": "pct-data-table[pctTableWindow]",
          "detail": null,
          "description": "Draws only the lines in the table's view, and the space of the rest: a list of a hundred thousand rows lays out the forty a frame shows (0098).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTablePage",
          "kind": "directive",
          "selector": "pct-data-table[pctTablePage]",
          "detail": null,
          "description": "The rows the table is given are a page of more: rowOffset of them stand before it and rowTotal there are in all, so a reader is told \"row 43 of 1,001\", not \"row 3 of 21\".",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableRequest",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What a remote source is asked for: a block of rows, [from, to), in the order and under the question the table holds.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableAnswer",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What a remote source answers: the rows of the block asked for, and how many rows there are in all under the question.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableLoad",
          "kind": "type",
          "selector": null,
          "detail": "( request: PctTableRequest<Id>, signal: AbortSignal, ) => Promise<PctTableAnswer<T>>",
          "description": "Loads a block of rows. signal aborts when the table no longer needs the block: the order or the question changed, or the view left it before it came.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableRemote",
          "kind": "directive",
          "selector": "pct-data-table[pctTableRemote]",
          "detail": null,
          "description": "The rows come from a remote source a block at a time, as the view reaches them: one function, load, asked for the blocks of the lines the window draws and half a block on each side, each block once (0097).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableRemoteParts",
          "kind": "component",
          "selector": null,
          "detail": null,
          "description": "The parts a remote source's failure is drawn with: the sentence and the retry of the block, in the first hole drawn of it or where an empty table says why. Declared here, drawn by the table, so a table that loads nothing from a server carries none of it (0097). pctTableRemote makes it, so it has no selector: there is no tag to write.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctFilterContext",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What a filter template is handed: the state the column holds, and how to change it.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctFilterTemplate",
          "kind": "directive",
          "selector": "ng-template[pctFilter]",
          "detail": null,
          "description": "A column's filter the application draws: the controls of its own state, in the column's panel and in the row of filters, decided by the column's filterBy. The table keeps the state and writes it into columnFilters as { own: state }; what the state is, is the application's.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableColumnFilters",
          "kind": "directive",
          "selector": "pct-data-table[pctTableColumnFilters]",
          "detail": null,
          "description": "Each column's own filter, in a panel its header's button opens, and in a row of filters under the headers when filterRow is on (0097, D5). The control follows the column: a text field for its words, two number fields for a range, two date fields for the days a column of instants falls on, a multiple select of the values the rows hold, or the application's own template, with a button that clears it.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableFilterParts",
          "kind": "component",
          "selector": null,
          "detail": null,
          "description": "The filters' parts: templates the table draws in its places — a header's button and its panel, a column's cell in the row of filters — and the handlers of the controls in them. Declared here, rendered there: the bindings are read in this component, the elements stand in the table. pctTableColumnFilters makes it, so it has no selector: there is no tag to write.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        }
      ],
      "parts": [
        {
          "name": "details",
          "description": "the data table's row of details below a row"
        },
        {
          "name": "disclosure",
          "description": "the data table's button that opens a row's details"
        },
        {
          "name": "group",
          "description": "the data table's heading of a group of rows"
        },
        {
          "name": "hole",
          "description": "the data table's row a remote source has not delivered"
        },
        {
          "name": "mark",
          "description": "the data table's word the quick filter found"
        },
        {
          "name": "message",
          "description": "the data table's sentence beside an empty table — see the data table's card"
        },
        {
          "name": "place",
          "description": "the data table's place of a sort key after the first"
        },
        {
          "name": "placeholder",
          "description": "the data table's row standing for rows still coming"
        },
        {
          "name": "filter",
          "description": "the data table's button that opens a column's filter"
        },
        {
          "name": "icon",
          "description": "the chevron beside a sortable header's label"
        },
        {
          "name": "toggle",
          "description": "the button that fills a sortable header cell"
        },
        {
          "name": "retry",
          "description": "the data table's button that asks a failed block again"
        },
        {
          "name": "error",
          "description": "the data table's error under the table, from its selection's form field"
        },
        {
          "name": "hint",
          "description": "the data table's hint under the table"
        },
        {
          "name": "select",
          "description": "the data table's checkbox or radio that picks a row"
        },
        {
          "name": "select-all",
          "description": "the data table's checkbox in the header that picks every row the filter left"
        },
        {
          "name": "warning",
          "description": "the data table's warning under the table"
        }
      ],
      "tokens": [
        {
          "name": "--pct-table-bg-changed",
          "type": "color",
          "ref": "{pct.warning-200}",
          "light": "#fde68a",
          "dark": "#b45309",
          "description": "Background a data table's cell flashes in when its value changes."
        },
        {
          "name": "--pct-table-bg-danger",
          "type": "color",
          "ref": "{pct.danger-100}",
          "light": "#fee2e2",
          "dark": "#991b1b",
          "description": "Background of a data table's row or cell in the danger tone."
        },
        {
          "name": "--pct-table-bg-hover",
          "type": "color",
          "ref": "{pct.primary-200}",
          "light": "#bfdbfe",
          "dark": "#1d4ed8",
          "description": "Background of a data table's row under the pointer."
        },
        {
          "name": "--pct-table-bg-info",
          "type": "color",
          "ref": "{pct.info-100}",
          "light": "#dbeafe",
          "dark": "#1e40af",
          "description": "Background of a data table's row or cell in the info tone."
        },
        {
          "name": "--pct-table-bg-selected",
          "type": "color",
          "ref": "{pct.primary-100}",
          "light": "#dbeafe",
          "dark": "#1e40af",
          "description": "Background of a data table's picked row."
        },
        {
          "name": "--pct-table-bg-striped",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of every other row of a striped data table."
        },
        {
          "name": "--pct-table-bg-success",
          "type": "color",
          "ref": "{pct.success-100}",
          "light": "#dcfce7",
          "dark": "#166534",
          "description": "Background of a data table's row or cell in the success tone."
        },
        {
          "name": "--pct-table-bg-warning",
          "type": "color",
          "ref": "{pct.warning-100}",
          "light": "#fef3c7",
          "dark": "#92400e",
          "description": "Background of a data table's row or cell in the warning tone."
        },
        {
          "name": "--pct-table-border",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Line under a row."
        },
        {
          "name": "--pct-table-caption-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the caption."
        },
        {
          "name": "--pct-table-caption-font-weight",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of the caption."
        },
        {
          "name": "--pct-table-caption-gap",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Space between the caption and the table."
        },
        {
          "name": "--pct-table-column-max-width",
          "type": "dimension",
          "ref": null,
          "light": "20rem",
          "dark": "20rem",
          "description": "Maximum width of a data table's column whose cells are cut to one line."
        },
        {
          "name": "--pct-table-details-bg",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of a data table's details row."
        },
        {
          "name": "--pct-table-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the cells."
        },
        {
          "name": "--pct-table-fg-danger",
          "type": "color",
          "ref": "{pct.on-danger-100}",
          "light": "#991b1b",
          "dark": "#fee2e2",
          "description": "Text colour of a data table's row or cell in the danger tone."
        },
        {
          "name": "--pct-table-fg-info",
          "type": "color",
          "ref": "{pct.on-info-100}",
          "light": "#1d4ed8",
          "dark": "#bfdbfe",
          "description": "Text colour of a data table's row or cell in the info tone."
        },
        {
          "name": "--pct-table-fg-muted",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the numbers of a data table's rows."
        },
        {
          "name": "--pct-table-fg-selected",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of a data table's picked row."
        },
        {
          "name": "--pct-table-fg-success",
          "type": "color",
          "ref": "{pct.on-success-100}",
          "light": "#166534",
          "dark": "#dcfce7",
          "description": "Text colour of a data table's row or cell in the success tone."
        },
        {
          "name": "--pct-table-fg-warning",
          "type": "color",
          "ref": "{pct.on-warning-100}",
          "light": "#92400e",
          "dark": "#fef3c7",
          "description": "Text colour of a data table's row or cell in the warning tone."
        },
        {
          "name": "--pct-table-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of the table."
        },
        {
          "name": "--pct-table-group-bg",
          "type": "color",
          "ref": "{pct.primary-100}",
          "light": "#dbeafe",
          "dark": "#1e40af",
          "description": "Background of a data table's group heading."
        },
        {
          "name": "--pct-table-group-fg",
          "type": "color",
          "ref": "{pct.on-primary-100}",
          "light": "#1d4ed8",
          "dark": "#bfdbfe",
          "description": "Text colour of a data table's group heading."
        },
        {
          "name": "--pct-table-heading-bg",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of a header cell."
        },
        {
          "name": "--pct-table-heading-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Line under the header row."
        },
        {
          "name": "--pct-table-heading-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of a header cell."
        },
        {
          "name": "--pct-table-heading-font-weight",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of a header cell."
        },
        {
          "name": "--pct-table-hint-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the hint under a data table that picks rows."
        },
        {
          "name": "--pct-table-hint-fg-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Text colour of the error under a data table that picks rows."
        },
        {
          "name": "--pct-table-hint-fg-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Text colour of the warning under a data table that picks rows."
        },
        {
          "name": "--pct-table-icon-fg-muted",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Colour of the chevron a sortable header shows as a hint."
        },
        {
          "name": "--pct-table-indent",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "How far a data table indents each level of its groups."
        },
        {
          "name": "--pct-table-mark-bg",
          "type": "color",
          "ref": "{pct.warning-200}",
          "light": "#fde68a",
          "dark": "#b45309",
          "description": "Background of a word the quick filter found."
        },
        {
          "name": "--pct-table-mark-fg",
          "type": "color",
          "ref": "{pct.on-warning-100}",
          "light": "#92400e",
          "dark": "#fef3c7",
          "description": "Text colour of a word the quick filter found."
        },
        {
          "name": "--pct-table-padding-x",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of a cell and a header cell."
        },
        {
          "name": "--pct-table-padding-y",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Vertical padding of a cell and a header cell."
        },
        {
          "name": "--pct-table-toggle-gap",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Gap between a sortable header's label and its chevron."
        },
        {
          "name": "--pct-table-toggle-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Touch-target floor of a sortable header."
        }
      ],
      "texts": [
        "fieldWarning",
        "tableClear",
        "tableDetails",
        "tableDetailsClose",
        "tableDetailsOpen",
        "tableEmpty",
        "tableFilter",
        "tableFiltered",
        "tableFrom",
        "tableGroupCount",
        "tableGroupEmpty",
        "tableGroupWithin",
        "tableLoadFailed",
        "tableLoading",
        "tableNo",
        "tableNoMatches",
        "tableRetry",
        "tableRowNumber",
        "tableSelectAll",
        "tableSelectRow",
        "tableSortAscending",
        "tableSortDescending",
        "tableSortLater",
        "tableTo",
        "tableTotal",
        "tableYes"
      ],
      "keyboard": "| key                 | effect                                                     | test                                                                                                                        |\n| ------------------- | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |\n| `Tab` / `Shift+Tab` | moves between the sort buttons, and onto a scrolling frame | `apps/sandbox-e2e/src/table.spec.ts` — \"an overflowing frame is a region named by the caption, and the keyboard scrolls it\" |\n| `Enter` / `Space`   | sorts by the column, or turns its order                    | `apps/sandbox-e2e/src/table.spec.ts` — \"the keyboard sorts: Enter sorts ascending, Space turns it\"                          |\n| `←` / `→`           | scrolls a focused frame along the inline axis              | `apps/sandbox-e2e/src/table.spec.ts` — the same case as `Tab`; the scrolling is the platform's                              |\n\nThere are no handlers to map: the sort buttons are buttons and the frame is a scrolling box,\nand both answer the keyboard on their own.",
      "decisions": [],
      "evidence": {
        "mutation": {
          "score": 100,
          "killed": 60,
          "survived": 0,
          "mutants": 60,
          "files": 1
        },
        "e2e": {
          "cases": 9,
          "spec": "apps/sandbox-e2e/src/table.spec.ts"
        },
        "pairs": 26,
        "baselines": 2,
        "cost": {
          "elements": 20,
          "depth": 4,
          "listeners": 0,
          "renders": 1,
          "micros": 11698,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "chart",
      "classes": [
        "PctChart"
      ],
      "role": "a scene of scales and the marks drawn on them",
      "category": "Data & status",
      "status": "released",
      "entrypoint": "@pacit/components/chart",
      "selectors": [
        "pct-chart",
        "pct-line",
        "pct-bars",
        "pct-band",
        "pct-axis",
        "pct-chart-table",
        "pct-chart-zoom",
        "pct-chart-legend",
        "[pctChartSync]"
      ],
      "harnesses": [
        "PctChartHarness",
        "PctLineHarness",
        "PctBarsHarness",
        "PctChartZoomHarness",
        "PctChartLegendHarness",
        "PctReferenceHarness",
        "PctSpanHarness",
        "PctNoteHarness",
        "PctNowHarness",
        "PctBandHarness",
        "PctPointsHarness",
        "PctAreaHarness",
        "PctRangeHarness",
        "PctBoxHarness"
      ],
      "docs": "/components/chart",
      "summary": "A chart you compose in the template: a scene that owns the axes and the colours, and marks inside it — lines and bars — that draw your rows. The data stand beside the picture as a table.",
      "usage": {
        "code": "<pct-chart #chart ariaLabel=\"Supply air, last 24 hours\" timeZone=\"Europe/Warsaw\" style=\"height: 320px\">\n  <pct-line [data]=\"readings\" x=\"at\" y=\"value\" name=\"Supply air\" unit=\"°C\" curve=\"step\" />\n</pct-chart>\n<pct-chart-table [chart]=\"chart\" />",
        "lang": "html"
      },
      "examples": [
        {
          "key": "area",
          "title": "Midnight in Warsaw on 28 October 2026. */",
          "code": "import { Component } from '@angular/core';\nimport { PctAxis, PctChart, PctLine } from '@pacit/components/chart';\n\nconst HOUR = 3_600_000;\n/** Midnight in Warsaw on 28 October 2026. */\nconst START = Date.UTC(2026, 9, 27, 23);\n\nconst outdoor = [\n  4.1, 3.8, 3.4, 3.1, 2.9, 2.8, 3.2, 4.4, 6.1, 7.9, 9.4, 10.6, 11.3, 11.6, 11.2,\n  10.1, 8.6, 7.4, 6.5, 5.9, 5.4, 5, 4.7, 4.4, 4.2,\n].map((value, hour) => ({ at: START + hour * HOUR, value }));\n\n/**\n * An area on a fixed axis\n *\n * One series filled down to zero, on an axis `pct-axis` holds from 0 to 30 °C whatever the\n * day brings, so two days side by side read on one scale. The fill is the series' palette\n * colour mixed into nothing by the skin's `--pct-chart-area-alpha`.\n */\n@Component({\n  selector: 'demo-chart-area',\n  imports: [PctChart, PctLine, PctAxis],\n  template: `\n    <pct-chart\n      ariaLabel=\"Outdoor temperature on 28 October 2026\"\n      description=\"Between 2.8 and 11.6 °C, warmest at 13:00.\"\n      timeZone=\"Europe/Warsaw\"\n      style=\"height: 220px\"\n    >\n      <pct-axis unit=\"°C\" [floor]=\"0\" [ceiling]=\"30\" />\n      <pct-line\n        [data]=\"outdoor\"\n        x=\"at\"\n        y=\"value\"\n        name=\"Outdoor\"\n        unit=\"°C\"\n        area\n      />\n    </pct-chart>\n  `,\n})\nexport class ChartAreaDemo {\n  protected readonly outdoor = outdoor;\n}"
        },
        {
          "key": "cursor",
          "title": "Midnight in Warsaw on 28 October 2026. */",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctChart, PctLine } from '@pacit/components/chart';\n\nconst HOUR = 3_600_000;\n/** Midnight in Warsaw on 28 October 2026. */\nconst START = Date.UTC(2026, 9, 27, 23);\n\nconst supply = [\n  18.2, 18, 17.8, 17.7, 17.9, 18.4, 19.1, 19.9, 20.6, 21.2, 21.7, 22.1, 22.4,\n  22.6, 22.5, 22.2, 21.8, 21.2, 20.6, 20, 19.4, 19, 18.6, 18.4, 18.3,\n].map((value, hour) => ({ at: START + hour * HOUR, value }));\nconst outdoor = [\n  4.1, 3.8, 3.4, 3.1, 2.9, 2.8, 3.2, 4.4, 6.1, 7.9, 9.4, 10.6, 11.3, 11.6, 11.2,\n  10.1, 8.6, 7.4, 6.5, 5.9, 5.4, 5, 4.7, 4.4, 4.2,\n].map((value, hour) => ({ at: START + hour * HOUR, value }));\n\n/**\n * A cursor to read\n *\n * Point at the chart, tap it, or press Tab and the arrow keys: the cursor steps from reading to\n * reading and the tooltip writes each series' value under it. The cursor is a model, so the line\n * below reads `reading()` and writes the same values outside the picture — what a legend of the\n * application's own would do.\n */\n@Component({\n  selector: 'demo-chart-cursor',\n  imports: [PctChart, PctLine],\n  styles: `\n    p {\n      min-block-size: 1.5em;\n      margin: var(--pct-space-3) 0 0;\n      color: var(--pct-text-muted);\n    }\n  `,\n  template: `\n    <pct-chart\n      #chart\n      ariaLabel=\"Supply and outdoor air on 28 October 2026\"\n      timeZone=\"Europe/Warsaw\"\n      style=\"height: 240px\"\n      [(cursor)]=\"cursor\"\n    >\n      <pct-line [data]=\"supply\" x=\"at\" y=\"value\" name=\"Supply air\" unit=\"°C\" />\n      <pct-line [data]=\"outdoor\" x=\"at\" y=\"value\" name=\"Outdoor\" unit=\"°C\" />\n    </pct-chart>\n    <p>\n      @if (chart.reading(); as reading) {\n        {{ reading.text }}: {{ reading.rows[0].text }} inside,\n        {{ reading.rows[1].text }} outside\n      } @else {\n        No cursor yet.\n      }\n    </p>\n  `,\n})\nexport class ChartCursorDemo {\n  protected readonly supply = supply;\n  protected readonly outdoor = outdoor;\n  protected readonly cursor = signal<number | null>(null);\n}"
        },
        {
          "key": "sync",
          "title": "Midnight in Warsaw on 28 October 2026. */",
          "code": "import { Component } from '@angular/core';\nimport { PctChart, PctChartSync, PctLine } from '@pacit/components/chart';\n\nconst HOUR = 3_600_000;\n/** Midnight in Warsaw on 28 October 2026. */\nconst START = Date.UTC(2026, 9, 27, 23);\n\nconst power = [\n  { at: START, value: 380 },\n  { at: START + 6 * HOUR, value: 1520 },\n  { at: START + 9 * HOUR, value: 2640 },\n  { at: START + 17 * HOUR, value: 1450 },\n  { at: START + 22 * HOUR, value: 380 },\n];\nconst heater = [\n  { at: START, value: 0 },\n  { at: START + 5 * HOUR, value: 1 },\n  { at: START + 8 * HOUR, value: 0 },\n  { at: START + 19 * HOUR, value: 1 },\n  { at: START + 21 * HOUR, value: 0 },\n];\n\n/**\n * Two charts in step\n *\n * A series over a band of states: two charts and `pctChartSync` on what holds them. Drag, pinch\n * or zoom either and the other follows; the cursor stands on both; and the plots share their\n * edges although the power's axis in W is wider than the heater's yes/no one.\n */\n@Component({\n  selector: 'demo-chart-sync',\n  imports: [PctChart, PctLine, PctChartSync],\n  styles: `\n    section {\n      display: grid;\n      gap: var(--pct-space-2);\n    }\n  `,\n  template: `\n    <section pctChartSync>\n      <pct-chart\n        ariaLabel=\"Heat pump power on 28 October 2026\"\n        timeZone=\"Europe/Warsaw\"\n        style=\"height: 200px\"\n      >\n        <pct-line\n          [data]=\"power\"\n          x=\"at\"\n          y=\"value\"\n          name=\"Power\"\n          unit=\"W\"\n          curve=\"step\"\n          [end]=\"end\"\n        />\n      </pct-chart>\n      <pct-chart\n        ariaLabel=\"Heater on 28 October 2026\"\n        timeZone=\"Europe/Warsaw\"\n        style=\"height: 90px\"\n        [zoomButtons]=\"false\"\n      >\n        <pct-line\n          [data]=\"heater\"\n          x=\"at\"\n          y=\"value\"\n          name=\"Heater\"\n          unit=\"boolean\"\n          curve=\"step\"\n          area\n          [end]=\"end\"\n        />\n      </pct-chart>\n    </section>\n  `,\n})\nexport class ChartSyncDemo {\n  protected readonly power = power;\n  protected readonly heater = heater;\n  protected readonly end = START + 24 * HOUR;\n}"
        },
        {
          "key": "legend",
          "title": "Midnight in Warsaw on 28 October 2026. */",
          "code": "import { Component } from '@angular/core';\nimport { PctChart, PctChartLegend, PctLine } from '@pacit/components/chart';\n\nconst HOUR = 3_600_000;\n/** Midnight in Warsaw on 28 October 2026. */\nconst START = Date.UTC(2026, 9, 27, 23);\n\nconst supply = [\n  18.2, 18, 17.8, 17.7, 17.9, 18.4, 19.1, 19.9, 20.6, 21.2, 21.7, 22.1, 22.4,\n  22.6, 22.5, 22.2, 21.8, 21.2, 20.6, 20, 19.4, 19, 18.6, 18.4, 18.3,\n].map((value, hour) => ({ at: START + hour * HOUR, value }));\nconst outdoor = [\n  4.1, 3.8, 3.4, 3.1, 2.9, 2.8, 3.2, 4.4, 6.1, 7.9, 9.4, 10.6, 11.3, 11.6, 11.2,\n  10.1, 8.6, 7.4, 6.5, 5.9, 5.4, 5, 4.7, 4.4, 4.2,\n].map((value, hour) => ({ at: START + hour * HOUR, value }));\nconst fan = [\n  { at: START, value: 380 },\n  { at: START + 7 * HOUR, value: 520 },\n  { at: START + 12 * HOUR, value: 640 },\n  { at: START + 18 * HOUR, value: 450 },\n];\n\n/**\n * A legend\n *\n * The series as a list beside the picture: each entry its swatch — the colour, the dash and\n * the marker the series is told apart by — its name, its unit where the chart holds more than\n * one, and the latest reading. Press an entry to hide the series and press it again to show\n * it; with a cursor on the chart the entries write the reading under it instead.\n */\n@Component({\n  selector: 'demo-chart-legend',\n  imports: [PctChart, PctLine, PctChartLegend],\n  styles: `\n    pct-chart-legend {\n      margin-block-start: var(--pct-space-3);\n    }\n  `,\n  template: `\n    <pct-chart\n      #chart\n      ariaLabel=\"Air and fan on 28 October 2026\"\n      timeZone=\"Europe/Warsaw\"\n      style=\"height: 240px\"\n    >\n      <pct-line [data]=\"supply\" x=\"at\" y=\"value\" name=\"Supply air\" unit=\"°C\" />\n      <pct-line [data]=\"outdoor\" x=\"at\" y=\"value\" name=\"Outdoor\" unit=\"°C\" />\n      <pct-line\n        [data]=\"fan\"\n        x=\"at\"\n        y=\"value\"\n        name=\"Fan power\"\n        unit=\"W\"\n        curve=\"step\"\n        [end]=\"end\"\n      />\n    </pct-chart>\n    <pct-chart-legend [chart]=\"chart\" value=\"latest\" />\n  `,\n})\nexport class ChartLegendDemo {\n  protected readonly supply = supply;\n  protected readonly outdoor = outdoor;\n  protected readonly fan = fan;\n  protected readonly end = START + 24 * HOUR;\n}"
        },
        {
          "key": "bars",
          "title": "Midnight in Warsaw on each day of March 2026 (winter time until the 29th). */",
          "code": "import { Component, computed, signal } from '@angular/core';\nimport {\n  PctBars,\n  PctChart,\n  PctChartTable,\n  type PctWindow,\n} from '@pacit/components/chart';\n\n/** Midnight in Warsaw on each day of March 2026 (winter time until the 29th). */\nconst day = (d: number) => Date.UTC(2026, 2, d - 1, d <= 29 ? 23 : 22);\nconst february = (d: number) => Date.UTC(2026, 1, d - 1, 23);\n\nconst march = [\n  11.2, 12.8, 9.4, 14.1, 13.3, 10.7, 8.9, 12.2, 15.6, 13.9, 11.8, 10.1, 9.7,\n  12.5, 14.8, 13.1, 11.4, 10.9, 9.2, 8.6, 10.4, 11.9, 12.7, 9.8, 8.3, 7.9, 9.1,\n  10.6, 8.8, 7.4, 6.9,\n].map((energy, i) => ({ at: day(i + 1), energy }));\nconst lastMonth = [\n  14.2, 15.1, 13.8, 16.4, 15.9, 14.7, 13.2, 15.8, 17.1, 16.3, 14.9, 13.6, 12.8,\n  14.4, 16.2, 15.7, 14.1, 13.3, 12.6, 11.9, 13.5, 14.8, 15.3, 12.9, 11.4, 10.8,\n  12.2, 13.1,\n].map((energy, i) => ({ at: february(i + 1), energy }));\n\n/**\n * Bars by day, compared with the month before\n *\n * A bar per calendar day of Warsaw, and February laid beside March by position: the first of\n * February next to the first of March, twenty-eight bars beside thirty-one. Press a bar to\n * select its day, or put the cursor on it and press Enter; the table under the picture has a\n * row per day and the month's sum in its foot.\n */\n@Component({\n  selector: 'demo-chart-bars',\n  imports: [PctChart, PctBars, PctChartTable],\n  styles: `\n    p {\n      margin: var(--pct-space-3) 0;\n      color: var(--pct-text-muted);\n    }\n  `,\n  template: `\n    <pct-chart\n      #chart\n      ariaLabel=\"Heat pump energy by day, March 2026\"\n      timeZone=\"Europe/Warsaw\"\n      interval=\"day\"\n      style=\"height: 260px\"\n      [(selected)]=\"selected\"\n    >\n      <pct-bars\n        [data]=\"march\"\n        x=\"at\"\n        y=\"energy\"\n        name=\"March\"\n        unit=\"kWh\"\n        [compare]=\"lastMonth\"\n        compareName=\"February\"\n      />\n    </pct-chart>\n    <p>{{ selectedText() }}</p>\n    <pct-chart-table [chart]=\"chart\" summary=\"sum\" [rows]=\"31\" />\n  `,\n})\nexport class ChartBarsDemo {\n  protected readonly march = march;\n  protected readonly lastMonth = lastMonth;\n  protected readonly selected = signal<PctWindow | null>(null);\n  protected readonly selectedText = computed(() => {\n    const band = this.selected();\n    if (!band) return 'Nothing selected.';\n    const day = new Intl.DateTimeFormat('en-GB', {\n      day: 'numeric',\n      month: 'long',\n      timeZone: 'Europe/Warsaw',\n    }).format(band.from);\n    return `Selected: ${day}`;\n  });\n}"
        },
        {
          "key": "annotations",
          "title": "Midnight in Warsaw on 28 October 2026. */",
          "code": "import { Component } from '@angular/core';\nimport {\n  PctChart,\n  PctChartLegend,\n  PctLine,\n  PctNote,\n  PctNow,\n  PctReference,\n  PctSpan,\n  type PctThreshold,\n} from '@pacit/components/chart';\n\nconst HOUR = 3_600_000;\n/** Midnight in Warsaw on 28 October 2026. */\nconst START = Date.UTC(2026, 9, 27, 23);\n\nconst supply = [\n  18.2, 18, 17.8, 17.7, 17.9, 18.4, 19.1, 19.9, 20.6, 21.2, 21.7, 22.1, 22.4,\n  22.6, 22.5, 22.2, 21.8, 21.2, 20.6, 20, 19.4, 19, 18.6, 18.4, 18.3,\n].map((value, hour) => ({ at: START + hour * HOUR, value }));\n\n/**\n * Annotations\n *\n * The marks that bring no series: a band under the line for the night tariff, a dashed\n * reference line at the comfort setting, a note on the hour the window was opened, the moment\n * the report was taken, and the line itself in another colour over 22 °C (`threshold`). None\n * has an axis or a column in the table; a reader hears them in the picture's description, and\n * the two asked to stand in the legend can be hidden there.\n */\n@Component({\n  selector: 'demo-chart-annotations',\n  imports: [\n    PctChart,\n    PctLine,\n    PctReference,\n    PctSpan,\n    PctNote,\n    PctNow,\n    PctChartLegend,\n  ],\n  styles: `\n    pct-chart-legend {\n      margin-block-start: var(--pct-space-3);\n    }\n  `,\n  template: `\n    <pct-chart\n      #chart\n      ariaLabel=\"Supply air on 28 October 2026, annotated\"\n      timeZone=\"Europe/Warsaw\"\n      style=\"height: 240px\"\n    >\n      <pct-span\n        [from]=\"night.from\"\n        [to]=\"night.to\"\n        label=\"Night tariff\"\n        legend\n      />\n      <pct-line\n        [data]=\"supply\"\n        x=\"at\"\n        y=\"value\"\n        name=\"Supply air\"\n        unit=\"°C\"\n        [threshold]=\"hot\"\n      />\n      <pct-reference [y]=\"21\" unit=\"°C\" label=\"Comfort\" dash legend />\n      <pct-note [x]=\"opened\" [y]=\"20.6\" unit=\"°C\" text=\"Window opened\" />\n      <pct-now [at]=\"reported\" label=\"Report taken\" />\n    </pct-chart>\n    <pct-chart-legend [chart]=\"chart\" />\n  `,\n})\nexport class ChartAnnotationsDemo {\n  protected readonly supply = supply;\n  protected readonly night = { from: START, to: START + 6 * HOUR };\n  protected readonly opened = START + 8 * HOUR;\n  protected readonly reported = START + 16 * HOUR;\n  protected readonly hot: PctThreshold[] = [{ at: 22, color: 'tomato' }];\n}"
        },
        {
          "key": "band",
          "title": "Midnight in Warsaw on 28 October 2026. */",
          "code": "import { Component } from '@angular/core';\nimport {\n  PctBand,\n  type PctBandState,\n  PctChart,\n  PctChartLegend,\n  PctChartTable,\n  PctLine,\n} from '@pacit/components/chart';\n\nconst HOUR = 3_600_000;\n/** Midnight in Warsaw on 28 October 2026. */\nconst START = Date.UTC(2026, 9, 27, 23);\n\nconst supply = [\n  18.2, 18, 17.8, 17.7, 17.9, 18.4, 19.1, 19.9, 20.6, 21.2, 21.7, 22.1, 22.4,\n  22.6, 22.5, 22.2, 21.8, 21.2, 20.6, 20, 19.4, 19, 18.6, 18.4, 18.3,\n].map((value, hour) => ({ at: START + hour * HOUR, value }));\n\n/** The heat pump's mode: what it was doing, from when to when. */\nconst modes = [\n  ['off', 0, 5],\n  ['heating', 5, 7.5],\n  ['defrost', 7.5, 8],\n  ['heating', 8, 9],\n  ['standby', 9, 16],\n  ['heating', 16, 20],\n  ['defrost', 20, 20.25],\n  ['heating', 20.25, 22],\n  ['off', 22, 24],\n].map(([mode, from, to]) => ({\n  mode: mode as string,\n  from: START + (from as number) * HOUR,\n  to: START + (to as number) * HOUR,\n}));\n\n/**\n * A band of states\n *\n * `pct-band` draws the heat pump's mode as a block per segment in a lane of its own under\n * the plot: no series, no axis. The tooltip names the mode under the cursor and how long it\n * held, the legend lists the modes with a toggle each, the table gets a column with a row per\n * segment, and a reader hears how long each mode held. A chart holding a band alone is a\n * band of states with a time axis; `lane=\"plot\"` tints the whole plot behind the series\n * instead.\n */\n@Component({\n  selector: 'demo-chart-band',\n  imports: [PctChart, PctLine, PctBand, PctChartLegend, PctChartTable],\n  styles: `\n    pct-chart-legend,\n    pct-chart-table {\n      margin-block-start: var(--pct-space-3);\n    }\n  `,\n  template: `\n    <pct-chart\n      #chart\n      ariaLabel=\"Supply air and the heat pump's mode on 28 October 2026\"\n      timeZone=\"Europe/Warsaw\"\n      style=\"height: 240px\"\n    >\n      <pct-line [data]=\"supply\" x=\"at\" y=\"value\" name=\"Supply air\" unit=\"°C\" />\n      <pct-band\n        [data]=\"modes\"\n        from=\"from\"\n        to=\"to\"\n        state=\"mode\"\n        label=\"Mode\"\n        [states]=\"states\"\n      />\n    </pct-chart>\n    <pct-chart-legend [chart]=\"chart\" />\n    <pct-chart-table [chart]=\"chart\" [rows]=\"12\" />\n  `,\n})\nexport class ChartBandDemo {\n  protected readonly supply = supply;\n  protected readonly modes = modes;\n  protected readonly states: Record<string, PctBandState> = {\n    heating: { color: 1, label: 'Heating' },\n    defrost: { color: 'tomato', label: 'Defrost' },\n    standby: { color: 3, label: 'Standby' },\n    off: { color: 'lightgray', label: 'Off' },\n  };\n}"
        },
        {
          "key": "points",
          "title": "Eighty days of a heat pump: the temperature outside against the power it drew. */",
          "code": "import { Component, signal } from '@angular/core';\nimport {\n  PctAxis,\n  PctChart,\n  PctChartLegend,\n  PctChartTable,\n  type PctPick,\n  PctPoints,\n  type PctWindow,\n} from '@pacit/components/chart';\n\ninterface Day {\n  readonly outdoor: number;\n  readonly power: number;\n}\n\n/** Eighty days of a heat pump: the temperature outside against the power it drew. */\nconst days: Day[] = Array.from({ length: 80 }, (_, i) => {\n  const outdoor = -8 + ((i * 37) % 97) / 4;\n  const noise = (((i * 53) % 29) - 14) / 20;\n  return {\n    outdoor: Math.round(outdoor * 10) / 10,\n    power:\n      Math.round((Math.max(0.3, 3.2 - outdoor * 0.14) + noise) * 100) / 100,\n  };\n});\n\ninterface Room {\n  readonly name: string;\n  readonly area: number;\n  readonly setpoint: number;\n  readonly occupancy: number;\n}\n\nconst rooms: Room[] = [\n  { name: 'Living room', area: 32, setpoint: 21, occupancy: 65 },\n  { name: 'Kitchen', area: 14, setpoint: 20, occupancy: 40 },\n  { name: 'Bedroom', area: 18, setpoint: 18, occupancy: 45 },\n  { name: 'Study', area: 12, setpoint: 21.5, occupancy: 55 },\n  { name: 'Bathroom', area: 6, setpoint: 23, occupancy: 10 },\n  { name: 'Hall', area: 9, setpoint: 19, occupancy: 20 },\n  { name: 'Guest room', area: 15, setpoint: 17, occupancy: 5 },\n];\n\n/**\n * Points, bubbles and a zoom on both axes\n *\n * `pct-points` draws a mark per row where its `x` and `y` put it, in any order: the cursor\n * stands on the nearest point, a press on one emits `pick` with its row, and `xUnit` writes\n * the unit of the horizontal axis. With `size` read off the rows the marks are bubbles whose\n * area grows with it, and the size is a column of the table and a row of the tooltip.\n * `zoom=\"xy\"` makes Ctrl with the wheel, a pinch or a drag work the first value axis too,\n * through the `yWindow` model; `0` returns both axes.\n */\n@Component({\n  selector: 'demo-chart-points',\n  imports: [PctChart, PctAxis, PctPoints, PctChartLegend, PctChartTable],\n  styles: `\n    pct-chart-legend,\n    pct-chart-table,\n    p {\n      margin-block-start: var(--pct-space-3);\n    }\n  `,\n  template: `\n    <pct-chart\n      #heating\n      ariaLabel=\"Power drawn against the temperature outside, eighty days\"\n      x=\"linear\"\n      xUnit=\"°C\"\n      zoom=\"xy\"\n      [(yWindow)]=\"yWindow\"\n      style=\"height: 260px\"\n    >\n      <pct-axis unit=\"kW\" [floor]=\"0\" />\n      <pct-points\n        [data]=\"days\"\n        x=\"outdoor\"\n        y=\"power\"\n        name=\"Power drawn\"\n        unit=\"kW\"\n        (pick)=\"picked.set($event)\"\n      />\n    </pct-chart>\n    <p>{{ pickedText() }}</p>\n    <pct-chart-legend [chart]=\"heating\" />\n\n    <pct-chart\n      #rooms\n      ariaLabel=\"Each room's setpoint against how often it is used, the bubble its area\"\n      x=\"linear\"\n      xUnit=\"%\"\n      style=\"height: 260px\"\n    >\n      <pct-points\n        [data]=\"roomRows\"\n        x=\"occupancy\"\n        y=\"setpoint\"\n        size=\"area\"\n        sizeName=\"Area\"\n        sizeUnit=\"m²\"\n        name=\"Setpoint\"\n        unit=\"°C\"\n        [color]=\"4\"\n      />\n    </pct-chart>\n    <pct-chart-table [chart]=\"rooms\" />\n  `,\n})\nexport class ChartPointsDemo {\n  protected readonly days = days;\n  protected readonly roomRows = rooms;\n  protected readonly yWindow = signal<PctWindow | null>(null);\n  protected readonly picked = signal<PctPick<Day> | null>(null);\n\n  protected pickedText(): string {\n    const pick = this.picked();\n    return pick\n      ? `Picked: ${pick.row.outdoor} °C outside, ${pick.row.power} kW drawn.`\n      : 'Press a point to pick its day.';\n  }\n}"
        },
        {
          "key": "areas",
          "title": "A day's power by source, every quarter of an hour; the boiler's meter was offline for an hour. */",
          "code": "import { Component } from '@angular/core';\nimport {\n  PctArea,\n  PctChart,\n  PctChartLegend,\n  PctLine,\n} from '@pacit/components/chart';\n\ninterface Quarter {\n  readonly at: number;\n  readonly gas: number | null;\n  readonly pump: number;\n  readonly solar: number;\n}\n\nconst START = Date.UTC(2026, 9, 24, 22);\nconst QUARTER = 15 * 60_000;\n\n/** A day's power by source, every quarter of an hour; the boiler's meter was offline for an hour. */\nconst quarters: Quarter[] = Array.from({ length: 97 }, (_, i) => {\n  const hours = (i / 4 + 22) % 24;\n  const sun = Math.max(0, Math.sin(((hours - 6) / 12) * Math.PI));\n  const cold = 1 + 0.5 * Math.cos(((hours - 4) / 24) * 2 * Math.PI);\n  const wobble = ((i * 29) % 17) / 40;\n  return {\n    at: START + i * QUARTER,\n    gas:\n      hours >= 14 && hours < 15\n        ? null\n        : Math.round((1.2 * cold + wobble) * 100) / 100,\n    pump: Math.round((0.8 * cold + wobble / 2) * 100) / 100,\n    solar: Math.round(sun * (2.4 + wobble) * 100) / 100,\n  };\n});\n\n/** The charge of a battery over forty minutes: a curve the eye wants smooth. */\nconst charge = Array.from({ length: 17 }, (_, i) => ({\n  at: i * 2.5,\n  value: Math.round(100 * (1 - Math.exp(-i / 5)) * 10) / 10,\n}));\n\n/**\n * Areas on a stack, as shares, and a monotone curve\n *\n * `pct-area` draws a series from zero, or on a `stack`: areas naming one stack stand on each\n * other, the sum the picture's top; a gap in one is a zero for the ones above it and a gap for\n * itself, and a hidden series leaves the stack. The tooltip reads each series' own value and\n * the marker stands on the drawing. `normalize` draws the stack as shares of each position's\n * total, a hundred tall, and reads the share with the value in brackets. `curve=\"monotone\"`\n * joins readings with a cubic that never overshoots them, and the tooltip reads that curve.\n */\n@Component({\n  selector: 'demo-chart-areas',\n  imports: [PctChart, PctArea, PctLine, PctChartLegend],\n  styles: `\n    pct-chart-legend,\n    pct-chart + pct-chart {\n      margin-block-start: var(--pct-space-3);\n    }\n  `,\n  template: `\n    <pct-chart\n      #stacked\n      ariaLabel=\"Power by source on 25 October 2026\"\n      timeZone=\"Europe/Warsaw\"\n      style=\"height: 260px\"\n    >\n      <pct-area\n        [data]=\"quarters\"\n        x=\"at\"\n        y=\"gas\"\n        name=\"Gas boiler\"\n        unit=\"kW\"\n        stack=\"power\"\n        curve=\"monotone\"\n      />\n      <pct-area\n        [data]=\"quarters\"\n        x=\"at\"\n        y=\"pump\"\n        name=\"Heat pump\"\n        unit=\"kW\"\n        stack=\"power\"\n        curve=\"monotone\"\n      />\n      <pct-area\n        [data]=\"quarters\"\n        x=\"at\"\n        y=\"solar\"\n        name=\"Solar\"\n        unit=\"kW\"\n        stack=\"power\"\n        curve=\"monotone\"\n      />\n    </pct-chart>\n    <pct-chart-legend [chart]=\"stacked\" />\n\n    <pct-chart\n      ariaLabel=\"Share of the power by source on 25 October 2026\"\n      timeZone=\"Europe/Warsaw\"\n      style=\"height: 220px\"\n    >\n      <pct-area\n        [data]=\"quarters\"\n        x=\"at\"\n        y=\"gas\"\n        name=\"Gas boiler\"\n        unit=\"kW\"\n        stack=\"share\"\n        normalize\n      />\n      <pct-area\n        [data]=\"quarters\"\n        x=\"at\"\n        y=\"pump\"\n        name=\"Heat pump\"\n        unit=\"kW\"\n        stack=\"share\"\n      />\n      <pct-area\n        [data]=\"quarters\"\n        x=\"at\"\n        y=\"solar\"\n        name=\"Solar\"\n        unit=\"kW\"\n        stack=\"share\"\n      />\n    </pct-chart>\n\n    <pct-chart\n      ariaLabel=\"State of charge against minutes\"\n      x=\"linear\"\n      xUnit=\"min\"\n      style=\"height: 200px\"\n    >\n      <pct-line\n        [data]=\"charge\"\n        x=\"at\"\n        y=\"value\"\n        name=\"Charge\"\n        unit=\"%\"\n        curve=\"monotone\"\n        area\n      />\n    </pct-chart>\n  `,\n})\nexport class ChartAreasDemo {\n  protected readonly quarters = quarters;\n  protected readonly charge = charge;\n}"
        },
        {
          "key": "ranges",
          "title": "A small deterministic sequence in [0, 1): the same picture on every load. */",
          "code": "import { Component } from '@angular/core';\nimport {\n  PctBars,\n  pctBin,\n  PctBox,\n  PctChart,\n  PctChartTable,\n  PctLine,\n  PctRange,\n} from '@pacit/components/chart';\n\n/** A small deterministic sequence in [0, 1): the same picture on every load. */\nfunction seeded(seed: number): () => number {\n  let state = seed;\n  return () => {\n    state = (state * 1_103_515_245 + 12_345) % 2_147_483_648;\n    return state / 2_147_483_648;\n  };\n}\n\n/** Each day of March 2026: the low and the high outside, and their mean. */\nconst march = (() => {\n  const random = seeded(29);\n  return Array.from({ length: 31 }, (_, i) => {\n    const trend = 2 + i * 0.25;\n    const low = Math.round((trend - 3 - random() * 3) * 10) / 10;\n    const high = Math.round((trend + 4 + random() * 4) * 10) / 10;\n    return {\n      at: Date.UTC(2026, 2, i + 1),\n      low,\n      high,\n      mean: Math.round(((low + high) / 2) * 10) / 10,\n    };\n  });\n})();\n\n/** Thirty sessions of a share: open, high, low and close. */\nconst sessions = (() => {\n  const random = seeded(31);\n  let close = 42;\n  return Array.from({ length: 30 }, (_, i) => {\n    const open = close;\n    close = Math.round((open + (random() - 0.48) * 3) * 100) / 100;\n    return {\n      at: Date.UTC(2026, 8, 1 + i),\n      open,\n      close,\n      high: Math.round((Math.max(open, close) + random() * 1.5) * 100) / 100,\n      low: Math.round((Math.min(open, close) - random() * 1.5) * 100) / 100,\n    };\n  });\n})();\n\n/** Each hour's response times: forty a batch, busier in the office hours, a long tail. */\nconst hourly = (() => {\n  const random = seeded(37);\n  return Array.from({ length: 24 }, (_, h) => ({\n    at: Date.UTC(2026, 9, 25, h),\n    times: Array.from({ length: 40 }, () => {\n      const base = (35 + random() * 50) * (h >= 9 && h <= 17 ? 1.6 : 1);\n      return Math.round(random() < 0.08 ? base + random() * 300 : base);\n    }),\n  }));\n})();\n\n/** Four hundred response times binned by Sturges' rule: a histogram on bars. */\nconst bins = pctBin(hourly.flatMap((h) => h.times).slice(0, 400));\n\n/**\n * A range, candles, boxes and a histogram\n *\n * `pct-range` draws a band from a `low` to a `high` at each position and reads both as\n * `low – high`; with an `open` and a `close` too it draws candles, rising in the skin's\n * `rise` colour and falling in its `fall`, the open, high and low on lines of the tooltip\n * and in columns of the table. `pct-box` draws each row's `values` as a box: the quartiles,\n * the median, whiskers within one and a half interquartile ranges, and the outliers as\n * points. A histogram is `pctBin` feeding the categories of a band axis and `pct-bars`.\n */\n@Component({\n  selector: 'demo-chart-ranges',\n  imports: [PctChart, PctRange, PctLine, PctBox, PctBars, PctChartTable],\n  styles: `\n    pct-chart-table,\n    pct-chart + pct-chart {\n      margin-block-start: var(--pct-space-3);\n    }\n  `,\n  template: `\n    <pct-chart\n      ariaLabel=\"Temperatures outside in March 2026\"\n      timeZone=\"Europe/Warsaw\"\n      style=\"height: 220px\"\n    >\n      <pct-range\n        [data]=\"march\"\n        x=\"at\"\n        low=\"low\"\n        high=\"high\"\n        name=\"Day's range\"\n        unit=\"°C\"\n        curve=\"monotone\"\n      />\n      <pct-line\n        [data]=\"march\"\n        x=\"at\"\n        y=\"mean\"\n        name=\"Mean\"\n        unit=\"°C\"\n        curve=\"monotone\"\n        color=\"2\"\n      />\n    </pct-chart>\n\n    <pct-chart\n      #candles\n      ariaLabel=\"PCT share in September 2026\"\n      timeZone=\"Europe/Warsaw\"\n      style=\"height: 220px\"\n    >\n      <pct-range\n        [data]=\"sessions\"\n        x=\"at\"\n        low=\"low\"\n        high=\"high\"\n        open=\"open\"\n        close=\"close\"\n        name=\"PCT\"\n        unit=\"€\"\n      />\n    </pct-chart>\n    <pct-chart-table [chart]=\"candles\" [rows]=\"5\" />\n\n    <pct-chart\n      ariaLabel=\"Response times by hour on 25 October 2026\"\n      timeZone=\"Europe/Warsaw\"\n      style=\"height: 240px\"\n    >\n      <pct-box\n        [data]=\"hourly\"\n        x=\"at\"\n        values=\"times\"\n        name=\"Response time\"\n        unit=\"ms\"\n      />\n    </pct-chart>\n\n    <pct-chart\n      ariaLabel=\"Response times by bin\"\n      x=\"band\"\n      [categories]=\"binLabels\"\n      style=\"height: 200px\"\n    >\n      <pct-bars [data]=\"binRows\" x=\"bin\" y=\"count\" name=\"Requests\" />\n    </pct-chart>\n  `,\n})\nexport class ChartRangesDemo {\n  protected readonly march = march;\n  protected readonly sessions = sessions;\n  protected readonly hourly = hourly;\n  protected readonly binLabels = bins.map((b) => `${b.from}–${b.to} ms`);\n  protected readonly binRows = bins.map((b, i) => ({ bin: i, count: b.count }));\n}"
        }
      ],
      "api": [
        {
          "name": "PctChart",
          "kind": "component",
          "selector": "pct-chart",
          "description": "A chart: a scene of scales and the marks drawn on them (0099). The marks — pct-line and those that follow it — stand inside it; the scene owns the scales, the window, the colours and the axes, and draws in layers: the data in one <svg>, the axes in a second, each on a compositor layer of its own, so a change of one does not repaint the other (0100).",
          "members": [
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The chart's name, which a reader announces with the picture, and which the data table takes as its caption. A picture with no name is a hole in the page: give this or ariaLabelledby.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "ariaLabelledby",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "As ariaLabel, for a name that already stands on the page: a heading over the chart.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "description",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "What the picture shows, in a sentence or two — the trend, the range, what stands out. A reader hears it after the name.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "x",
              "kind": "input",
              "required": false,
              "type": "'time' | 'linear' | 'band'",
              "default": "'time'",
              "description": "What the horizontal axis holds: instants (time, milliseconds since the epoch) or plain numbers (linear).",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "xUnit",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The unit of a linear horizontal axis: written at the axis's end and after the position the cursor reads, as a value axis's unit is; '' for none.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "categories",
              "kind": "input",
              "required": false,
              "type": "readonly string[]",
              "default": "[]",
              "description": "The categories of a band axis, in order: each a band the bars stand in, labelled by it. A series on such an axis gives x as the category's index (its middle is index + 0.5).",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "interval",
              "kind": "input",
              "required": false,
              "type": "PctInterval | undefined",
              "default": "undefined",
              "description": "The calendar interval the bars of a time axis stand in — each bar a whole hour, day, week, month or year of timeZone. A time axis without one holds no bars.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "timeZone",
              "kind": "input",
              "required": false,
              "type": "string | undefined",
              "default": "undefined",
              "description": "The zone whose wall clock the time axis reads: its ticks fall on its whole hours and midnights. The runtime's own when unset — under SSR the server's, so pass it.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "orientation",
              "kind": "input",
              "required": false,
              "type": "'vertical' | 'horizontal'",
              "default": "'vertical'",
              "description": "Which way the x axis runs. vertical, the default, lays it along the bottom with the values up the sides; horizontal lays it down the left side with the values along the bottom — bars grow to the right, the cursor's line lies across, a drag up and down pans.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "locale",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "Overrides the application's LOCALE_ID for the numbers and times the axes write.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "grid",
              "kind": "input",
              "required": false,
              "type": "'first' | 'all' | 'none'",
              "default": "'first'",
              "description": "Which value axes draw grid lines: the first, every one, or none.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "loading",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "The data are on their way: the chart shows a skeleton of its shape and tells a reader it is busy.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "window",
              "kind": "model",
              "required": false,
              "type": "PctWindow | null",
              "default": "null",
              "description": "The stretch of the horizontal axis on show, or null for all the data have (0101). A change of the data changes it not.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "yWindow",
              "kind": "model",
              "required": false,
              "type": "PctWindow | null",
              "default": "null",
              "description": "The stretch of the first value axis on show while zoom is xy, in that axis's unit, or null for the domain its data give it. The other axes follow it in pixels, so a zoom on two axes keeps the series over one another where they were (0105).",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "hiddenSeries",
              "kind": "model",
              "required": false,
              "type": "readonly string[]",
              "default": "[]",
              "description": "The ids of the series not drawn. A hidden series keeps its colour and its axis, and adds nothing to the axis's domain.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "cursor",
              "kind": "model",
              "required": false,
              "type": "number | null",
              "default": "null",
              "description": "Where the cursor stands on the horizontal axis — an instant or a number, as x says — or null for none (0101). The pointer, the keys and a reader move it; a change of the window or of the data leaves it where it is.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "selected",
              "kind": "model",
              "required": false,
              "type": "PctWindow | null",
              "default": "null",
              "description": "The interval selected on the chart, or null: a tap on a bar's band selects it and a second lets it go, as Enter and Space do at the cursor; the application sets it too (0101).",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "cursorMode",
              "kind": "input",
              "required": false,
              "type": "'hover' | 'tap' | 'none'",
              "default": "'hover'",
              "description": "How the pointer moves the cursor. hover: a mouse's cursor follows it and a press pins it, a tap places it and a finger held still drags it. tap: only a press or a tap places it. none: no cursor at all and no tab stop — a picture alone.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "tooltip",
              "kind": "input",
              "required": false,
              "type": "'auto' | 'none'",
              "default": "'auto'",
              "description": "The tooltip by the cursor: auto writes the time and every series' value, or stamps the pctChartTooltip template when there is one; none draws none, for a legend that shows the reading instead.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "zoom",
              "kind": "input",
              "required": false,
              "type": "'x' | 'xy' | 'none'",
              "default": "'x'",
              "description": "How the window moves: x — a drag and a wheel with Ctrl pan and zoom it along the horizontal axis, two fingers pinch it, a double tap returns it, the keys and the zoom buttons do the same; xy — the same moves work the first value axis too, through yWindow, and Shift with an arrow up or down pans it; none — it stays where the application puts it, and a drag across the plot is the page's.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "distinct",
              "kind": "input",
              "required": false,
              "type": "'color' | 'all'",
              "default": "'color'",
              "description": "The lines draw their dash patterns always (all), or only where the colours are gone, under forced colours (color, the default): eight dashed lines are noise where the colours tell them apart, and the legend and the tooltip draw the pattern beside each name either way (0102).",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "defaultWindow",
              "kind": "input",
              "required": false,
              "type": "PctWindow | null",
              "default": "null",
              "description": "Where the window returns on a double tap, the 0 key and the zoom buttons' last one: a window, or null for all the data.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "zoomButtons",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "true",
              "description": "The chart draws its zoom buttons over the plot's corner while zoom is x: the single-pointer twin of the pinch and the drag. Off, render pct-chart-zoom elsewhere, or buttons of your own on zoomBy, panBy and home — removing them removes the twin.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "limits",
              "kind": "input",
              "required": false,
              "type": "PctWindow | null",
              "default": "null",
              "description": "How far the window can go: it stops at a limit and keeps its span. The data's own span when unset.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "minSpan",
              "kind": "input",
              "required": false,
              "type": "number | undefined",
              "default": "undefined",
              "description": "The narrowest window, in the axis's units: two minutes on a time axis, a millionth of the data's span on a numeric one, when unset.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "follow",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "The window follows the present: while its end touches now(), it moves with the clock every second and keeps its span. A window the reader moved back stays where it was put.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "now",
              "kind": "input",
              "required": false,
              "type": "() => number",
              "default": "() => Date.now()",
              "description": "The clock follow reads: Date.now unless a test or a replay gives another.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "[class.horizontal]",
              "kind": "property",
              "bound": "horizontal()",
              "note": ""
            },
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-chart",
              "note": ""
            },
            {
              "attribute": "aria-busy",
              "kind": "attribute",
              "bound": "loading() || !measured() ? \"true\" : null",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctAnnotation",
          "kind": "directive",
          "selector": null,
          "detail": null,
          "description": "What a reference line, a band, a note and the line of now share: a mark on the scene that brings no series — no axis, no column in the data table, no entry in the legend unless legend asks for one — and draws in a layer under the series or over them. It is told apart by a label and a color, and a reader hears it in the picture's description (0099).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctArea",
          "kind": "component",
          "selector": "pct-area",
          "detail": null,
          "description": "A series drawn as an area: alone, from zero to its value; on a stack, from the top of the areas under it to its own, so a sum reads as a whole and each part as a layer of it (0106). With normalize, every layer of the stack is its share of the position's total, in percent, and the picture is a hundred tall everywhere.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctBandState",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "How a state of a band is drawn and named: a palette place from 1 to 8 or any CSS colour, and the label the legend, the tooltip and the table write for the state's key.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctBand",
          "kind": "component",
          "selector": "pct-band",
          "detail": null,
          "description": "A band of states along the horizontal axis — a heat pump's mode through a day, a door open and shut, a machine running or idle: one block per segment of data, in the colour of its state. It brings no series and no value axis: it draws in a lane of its own under the plot (lane, a strip of the scene's band-height), as a tint across the plot behind the series (lane=\"plot\"), or, as the only mark of a scene, in the whole picture. The tooltip names the state under the cursor and how long it held, the legend lists the states with a toggle each, the data table gets a column with a row per segment, and a reader hears how long each state held in the window (0104).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctPick",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What a press on a bar picks: the series, the band's index and the row the bar was drawn from, with the bar's value and the band's start.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctBars",
          "kind": "component",
          "selector": "pct-bars",
          "detail": null,
          "description": "A series of bars, one per band of the scene — a calendar interval of timeZone or a category — drawn from the rows whose x falls in the band (0099). Where several rows fall in one band the last stands; a band with none has no bar, and a zero is a line. The bars of every pct-bars on the scene share each band: marks with the same group stand on each other when they share a stack, and side by side otherwise; a mark's own group when none is named.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctBox",
          "kind": "component",
          "selector": "pct-box",
          "detail": null,
          "description": "A distribution at each position, drawn as a box (0106): the quartiles as its ends, the median as a line across it, whiskers to the farthest values within one and a half interquartile ranges of it, and every value past them as a point of its own (pctQuantiles). A day's readings, a batch's measurements, a run's latencies.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctAxis",
          "kind": "directive",
          "selector": "pct-axis",
          "detail": null,
          "description": "A value axis the consumer configures by its unit. Without one, an axis takes its domain from the data in the window.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctAxisLayout",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "A value axis as the scene lays it out: what a legend or a custom axis reads off axes().",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctChartReading",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What the chart reads under its cursor: the position, written as the axis writes it, and every series shown there. What the tooltip draws and the cursor's range says, and what a legend of the application's own reads off reading().",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctChartReadingRow",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "One series' value under the cursor.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctChartTooltipContext",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The context of a pctChartTooltip template: the reading under the cursor.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctChartTooltip",
          "kind": "directive",
          "selector": "ng-template[pctChartTooltip]",
          "detail": null,
          "description": "A tooltip of the consumer's own: an <ng-template pctChartTooltip let-reading> inside the chart, stamped in place of the default rows, with the reading under the cursor.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctChart",
          "kind": "component",
          "selector": "pct-chart",
          "detail": null,
          "description": "A chart: a scene of scales and the marks drawn on them (0099). The marks — pct-line and those that follow it — stand inside it; the scene owns the scales, the window, the colours and the axes, and draws in layers: the data in one <svg>, the axes in a second, each on a compositor layer of its own, so a change of one does not repaint the other (0100).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctChartTable",
          "kind": "component",
          "selector": "pct-chart-table",
          "detail": null,
          "description": "The data of a chart as a table: the alternative a reader walks where the eye reads the picture (0103). It is the platform's <table> dressed by table[pctTable], captioned by the chart's name: a column for the position, and one per series shown, its unit in the header.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctChartZoom",
          "kind": "component",
          "selector": "pct-chart-zoom",
          "detail": null,
          "description": "The single-pointer twin of a chart's window gestures (0101): five buttons — in, out, earlier, later, back to the default window — calling the transform a pinch, a drag and the wheel call, so whoever cannot pinch or drag moves the window as far (WCAG 2.5.1, 2.5.7). It follows the APG toolbar pattern: one stop, the arrows between the buttons.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctLegendEntry",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "One entry of a legend: the series, its swatch and what the legend writes beside it.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctLegendItemContext",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The context of a pctLegendItem template: the entry, and what toggles it.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctLegendItem",
          "kind": "directive",
          "selector": "ng-template[pctLegendItem]",
          "detail": null,
          "description": "A row of the legend of the consumer's own: an <ng-template pctLegendItem let-entry let-toggle=\"toggle\"> inside pct-chart-legend, stamped in place of the default row, inside the list item.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctChartLegend",
          "kind": "component",
          "selector": "pct-chart-legend",
          "detail": null,
          "description": "The legend of a chart (0102): a list (the host) with an entry per series — its swatch, drawn by its mark with its colour, its dash and its marker, its name, its unit where the chart holds more than one, and a value — and a button per entry that hides the series or shows it again, writing the chart's hiddenSeries. The value is the reading under the cursor while there is one, and otherwise what value asks: the latest reading in the window, their sum, their mean, or nothing.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctThreshold",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "A value a line changes its colour at: from at up, the line is drawn in color, a palette place from 1 to 8 or any CSS colour.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctLine",
          "kind": "component",
          "selector": "pct-line",
          "detail": null,
          "description": "A series drawn as a line, a step or an area, on the chart it stands in (0099).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "pctSeriesColor",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Reads a series' color: a whole number from 1 to 8, or its text, is a palette place; any other text is a CSS colour; anything else is no colour, and the series takes a free place.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctNote",
          "kind": "component",
          "selector": "pct-note",
          "detail": null,
          "description": "A text on a point of the plot — \"Window opened\", \"Filter changed\" — in a box over the point or under it, with a leader to the point, drawn over the series. The scene places every note together so that none covers another and every one stays on the plot (pctPlaceNotes): position asks for a side, auto takes the first with room. It brings no series; a reader hears it in the picture's description (Window opened (25 October 2026 14:10, 19.2 °C).).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctNow",
          "kind": "component",
          "selector": "pct-now",
          "detail": null,
          "description": "The line of the present moment across the plot, moving with the clock once a second while the moment is in the window, with its label at the top. at pins it to a moment instead — the time a report was taken, or a test's clock. It brings no series; a reader hears it in the picture's description (Now 25 October 2026 14:10.).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctPoints",
          "kind": "component",
          "selector": "pct-points",
          "detail": null,
          "description": "A series of points on a chart — a scatter, or bubbles when size is read: each row a mark at its x and y, the series' marker shape in the series' colour (0105). No ARIA pattern covers a chart; the WAI tutorial \"Complex images\" does, and the scene (pct-chart) implements it for every mark.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctRange",
          "kind": "component",
          "selector": "pct-range",
          "detail": null,
          "description": "A series of two values at a time (0106): a low and a high drawn as a band between them — the day's temperatures, a forecast's spread, the bounds of a measurement — and, with an open and a close too, candles: a wick from the low to the high and a body from the open to the close, in the skin's rise colour where the close stands at or above the open and its fall colour where it stands under.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctReference",
          "kind": "component",
          "selector": "pct-reference",
          "detail": null,
          "description": "A line across the plot at a value — a limit, a set point, a mean — or at a position, with its label at its end, drawn over the series. It brings no series: no axis, no column in the table, and no legend entry unless legend asks for one; a reader hears it in the picture's description (Comfort 21 °C.).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctSeriesIdentity",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What a series is told apart by on the picture: its palette place, or a colour of the consumer's own (place custom, color set). The rows of a reading and the entries of a legend carry it, and a mark's swatch draws it.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctSwatchContext",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The context of a mark's swatch template: the series, and the form asked for.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PCT_SERIES_MARKERS",
          "kind": "const",
          "selector": null,
          "detail": null,
          "description": "The marker of each palette place, as a path in a box of twelve: a circle, a square, a triangle, a diamond, a triangle on its point, a plus, a hexagon and a star — eight shapes a reader with no colour tells apart, and the dash patterns of line.scss their twins (0102).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctSeriesSpec",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "A series as a mark declares it.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctChartScene",
          "kind": "directive",
          "selector": null,
          "detail": null,
          "description": "The scene as a mark sees it: what to draw with.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctMarkLayer",
          "kind": "type",
          "selector": null,
          "detail": "'back' | 'data' | 'front'",
          "description": "The layer of the data a mark draws in: back under the series (a band of the plot), data among them, front over them (a reference line, a note).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctLegendExtra",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "An entry a mark adds to the legend beside its series — an annotation with legend set. The legend lists it by its name with the mark's swatch, and its toggle writes the id into the chart's hiddenSeries, which the mark reads to hide.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctMark",
          "kind": "directive",
          "selector": null,
          "detail": null,
          "description": "A mark as the scene sees it.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctMarkNote",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "A line of the tooltip that is no series' value: a name and a text, written by a mark beside the rows (PctMark.notesAt). With a swatch it draws one as a marker, like a row, and a legend entry of the same id shows its text as the value; without one it is words alone, and the legend does not list it.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctMarkColumn",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "A column of text a mark adds to the data table (PctMark.columns): its id, which textAt is asked by, and its header.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctChartAlign",
          "kind": "directive",
          "selector": null,
          "detail": null,
          "description": "What aligns the plots of charts that share a time axis: the room the widest value axes take on each side. pctChartSync provides it; a scene takes the larger of its own room and this.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctSpan",
          "kind": "component",
          "selector": "pct-span",
          "detail": null,
          "description": "A band across the plot between two positions — a night tariff, a holiday — or between two values of a unit — a comfort range — drawn under the series, with its label in its corner. It brings no series: no axis, no column in the table, and no legend entry unless legend asks for one; a reader hears it in the picture's description (Night tariff 25 October 2026 22:00 – 26 October 2026 06:00.).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctChartShared",
          "kind": "type",
          "selector": null,
          "detail": "'window' | 'cursor'",
          "description": "What charts in sync can share.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctChartSync",
          "kind": "directive",
          "selector": "[pctChartSync]",
          "detail": null,
          "description": "Charts that move together (0101): on a common ancestor it ties the window and the cursor of every chart inside it, and gives their plots the same left and right edges, so two charts on one time axis whose value axes differ in width still line up — a series over a band of states is two charts and this.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        }
      ],
      "parts": [
        {
          "name": "area",
          "description": "the area under a line (pct-line with area), or a layer of pct-area"
        },
        {
          "name": "series",
          "description": "a line's path, drawn by pct-line, or an area's top edge by pct-area"
        },
        {
          "name": "swatch",
          "description": "a series drawn small by its mark: its line, dash and marker"
        },
        {
          "name": "band",
          "description": "a band of states, drawn by pct-band in its lane or across the plot"
        },
        {
          "name": "segment",
          "description": "one segment of a band, a block in its state's colour"
        },
        {
          "name": "bar",
          "description": "a bar, drawn by pct-bars into the chart's data layer"
        },
        {
          "name": "label",
          "description": "the value written over a bar, when labels is set"
        },
        {
          "name": "box",
          "description": "the box of pct-box from the lower quartile to the upper"
        },
        {
          "name": "median",
          "description": "the line across a box at its median"
        },
        {
          "name": "outlier",
          "description": "a value past a box's whiskers, drawn as a point"
        },
        {
          "name": "whisker",
          "description": "a line from a box to its farthest value within reach, and the cap there"
        },
        {
          "name": "axis",
          "description": "the <svg> of the axes, their lines, ticks and labels"
        },
        {
          "name": "cursor",
          "description": "the cursor's line across the plot"
        },
        {
          "name": "data",
          "description": "the <svg> the marks draw into"
        },
        {
          "name": "grid",
          "description": "the lines behind the data"
        },
        {
          "name": "marker",
          "description": "a series' value on the cursor's line"
        },
        {
          "name": "message",
          "description": "the message of a chart with nothing to draw"
        },
        {
          "name": "plot",
          "description": "the picture: the box with role=\"img\" holding the layers"
        },
        {
          "name": "range",
          "description": "the cursor's range: the chart's stop, hidden from the eye"
        },
        {
          "name": "selection",
          "description": "the selected band, across the plot"
        },
        {
          "name": "tooltip",
          "description": "the reading beside the cursor's line"
        },
        {
          "name": "unit",
          "description": "the unit written over a value axis"
        },
        {
          "name": "item",
          "description": "an entry of the legend, whose host is the list"
        },
        {
          "name": "remove",
          "description": "the button of an entry that asks to take the series off"
        },
        {
          "name": "toggle",
          "description": "the button of an entry that hides the series or shows it"
        },
        {
          "name": "zoom",
          "description": "a button of the zoom bar, on pct-chart-zoom"
        },
        {
          "name": "note",
          "description": "a note's dot, leader, box and text, drawn by pct-note over the series"
        },
        {
          "name": "now",
          "description": "the line of the present moment and its label, drawn by pct-now"
        },
        {
          "name": "point",
          "description": "one point: the series' marker shape at its row's place, a bubble by size"
        },
        {
          "name": "points",
          "description": "a series of points or bubbles, drawn by pct-points into the data layer"
        },
        {
          "name": "body",
          "description": "the box of a candle from its open to its close"
        },
        {
          "name": "candle",
          "description": "one candle of pct-range: its wick and its body, rising or falling"
        },
        {
          "name": "spread",
          "description": "the band of pct-range from its lows to its highs"
        },
        {
          "name": "wick",
          "description": "the line of a candle from its low to its high"
        },
        {
          "name": "reference",
          "description": "a reference line and its label, drawn by pct-reference over the series"
        },
        {
          "name": "span",
          "description": "a band across the plot and its label, drawn by pct-span under the series"
        }
      ],
      "tokens": [
        {
          "name": "--pct-chart-area-alpha",
          "type": "dimension",
          "ref": null,
          "light": "20%",
          "dark": "20%",
          "description": "How much of the series colour fills the area under a line."
        },
        {
          "name": "--pct-chart-axis-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Line of an axis and its ticks."
        },
        {
          "name": "--pct-chart-axis-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Labels of the ticks."
        },
        {
          "name": "--pct-chart-axis-gap",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Gap between an axis and its labels."
        },
        {
          "name": "--pct-chart-band-height",
          "type": "dimension",
          "ref": null,
          "light": "16px",
          "dark": "16px",
          "description": "A lane of states under the plot: the height of its band."
        },
        {
          "name": "--pct-chart-bar-alpha",
          "type": "dimension",
          "ref": null,
          "light": "55%",
          "dark": "55%",
          "description": "How much of the series colour fills a compared period's bar."
        },
        {
          "name": "--pct-chart-bar-gap",
          "type": "dimension",
          "ref": null,
          "light": "2px",
          "dark": "2px",
          "description": "Gap between the bars of a group."
        },
        {
          "name": "--pct-chart-bar-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of a bar's far end."
        },
        {
          "name": "--pct-chart-body-bg-fall",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "A candle that closes under its open (0106)."
        },
        {
          "name": "--pct-chart-body-bg-rise",
          "type": "color",
          "ref": "{pct.success}",
          "light": "#15803d",
          "dark": "#4ade80",
          "description": "A candle that closes above its open (0106)."
        },
        {
          "name": "--pct-chart-bubble-alpha",
          "type": "dimension",
          "ref": null,
          "light": "70%",
          "dark": "70%",
          "description": "How much of the series colour fills a bubble, so the ones under it show through."
        },
        {
          "name": "--pct-chart-cursor-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "The line of the cursor across the plot."
        },
        {
          "name": "--pct-chart-edge-width",
          "type": "dimension",
          "ref": null,
          "light": "1.5px",
          "dark": "1.5px",
          "description": "Width of the top edge of an area, where two layers of a stack meet (0106)."
        },
        {
          "name": "--pct-chart-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of the axis labels, the units and the empty message."
        },
        {
          "name": "--pct-chart-grid-border",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Lines of the grid behind the data."
        },
        {
          "name": "--pct-chart-label-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "The value written over a bar."
        },
        {
          "name": "--pct-chart-label-font-weight-selected",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of the label over the selected bar."
        },
        {
          "name": "--pct-chart-legend-fg-muted",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "The name of a hidden series in a legend."
        },
        {
          "name": "--pct-chart-legend-gap",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Gap between the entries of a legend."
        },
        {
          "name": "--pct-chart-marker-border",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "The ring around a marker on the cursor's line, which parts it from the line it stands on."
        },
        {
          "name": "--pct-chart-marker-size",
          "type": "dimension",
          "ref": null,
          "light": "10px",
          "dark": "10px",
          "description": "Diameter of a marker on the cursor's line: a drawing, no target, so it stays off the density axis."
        },
        {
          "name": "--pct-chart-message-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "The message of a chart with nothing to draw."
        },
        {
          "name": "--pct-chart-note-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of a note's box on the plot."
        },
        {
          "name": "--pct-chart-note-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Edge of a note's box, its leader and the dot on its point."
        },
        {
          "name": "--pct-chart-note-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text of a note on the plot."
        },
        {
          "name": "--pct-chart-now-border",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "The line of the present moment across the plot, and its label."
        },
        {
          "name": "--pct-chart-reference-border",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "A reference line across the plot, when its mark names no colour."
        },
        {
          "name": "--pct-chart-reference-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "The label written by a reference line or a band."
        },
        {
          "name": "--pct-chart-remove-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Touch-target floor of a legend entry's remove button."
        },
        {
          "name": "--pct-chart-selection-alpha",
          "type": "dimension",
          "ref": null,
          "light": "16%",
          "dark": "16%",
          "description": "How much of the selection colour fills the band."
        },
        {
          "name": "--pct-chart-selection-bg",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Colour of the selected band across the plot, before the mix."
        },
        {
          "name": "--pct-chart-series-color-1",
          "type": "color",
          "ref": "{pct.series-1}",
          "light": "#3848b7",
          "dark": "#708afb",
          "description": "Colour of series 1: its line, its area and its swatch."
        },
        {
          "name": "--pct-chart-series-color-2",
          "type": "color",
          "ref": "{pct.series-2}",
          "light": "#8f312c",
          "dark": "#de655c",
          "description": "Colour of series 2: its line, its area and its swatch."
        },
        {
          "name": "--pct-chart-series-color-3",
          "type": "color",
          "ref": "{pct.series-3}",
          "light": "#029b98",
          "dark": "#42dcd8",
          "description": "Colour of series 3: its line, its area and its swatch."
        },
        {
          "name": "--pct-chart-series-color-4",
          "type": "color",
          "ref": "{pct.series-4}",
          "light": "#af8002",
          "dark": "#f6b925",
          "description": "Colour of series 4: its line, its area and its swatch."
        },
        {
          "name": "--pct-chart-series-color-5",
          "type": "color",
          "ref": "{pct.series-5}",
          "light": "#773c6f",
          "dark": "#c36cb7",
          "description": "Colour of series 5: its line, its area and its swatch."
        },
        {
          "name": "--pct-chart-series-color-6",
          "type": "color",
          "ref": "{pct.series-6}",
          "light": "#058066",
          "dark": "#1cbf9a",
          "description": "Colour of series 6: its line, its area and its swatch."
        },
        {
          "name": "--pct-chart-series-color-7",
          "type": "color",
          "ref": "{pct.series-7}",
          "light": "#7a73d2",
          "dark": "#a7a5f3",
          "description": "Colour of series 7: its line, its area and its swatch."
        },
        {
          "name": "--pct-chart-series-color-8",
          "type": "color",
          "ref": "{pct.series-8}",
          "light": "#ab501f",
          "dark": "#ea8e62",
          "description": "Colour of series 8: its line, its area and its swatch."
        },
        {
          "name": "--pct-chart-series-width",
          "type": "dimension",
          "ref": null,
          "light": "2px",
          "dark": "2px",
          "description": "Width of a line."
        },
        {
          "name": "--pct-chart-span-alpha",
          "type": "dimension",
          "ref": null,
          "light": "12%",
          "dark": "12%",
          "description": "How much of the band colour fills a band across the plot."
        },
        {
          "name": "--pct-chart-span-bg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "A band across the plot, before the mix, when its mark names no colour."
        },
        {
          "name": "--pct-chart-swatch-height",
          "type": "dimension",
          "ref": null,
          "light": "12px",
          "dark": "12px",
          "description": "Height of a series' swatch in a legend."
        },
        {
          "name": "--pct-chart-swatch-width",
          "type": "dimension",
          "ref": null,
          "light": "24px",
          "dark": "24px",
          "description": "Width of a series' swatch in a legend: its line with its dash, its marker on it."
        },
        {
          "name": "--pct-chart-toggle-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Touch-target floor of a legend entry's toggle."
        },
        {
          "name": "--pct-chart-tooltip-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Background of the tooltip by the cursor."
        },
        {
          "name": "--pct-chart-tooltip-border",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge of the tooltip by the cursor."
        },
        {
          "name": "--pct-chart-tooltip-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text of the tooltip by the cursor."
        },
        {
          "name": "--pct-chart-tooltip-padding-x",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of the tooltip."
        },
        {
          "name": "--pct-chart-tooltip-padding-y",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Vertical padding of the tooltip."
        },
        {
          "name": "--pct-chart-tooltip-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the tooltip."
        },
        {
          "name": "--pct-chart-tooltip-shadow",
          "type": "shadow",
          "ref": "{pct.shadow-panel}",
          "light": "0 8px 24px rgba(15, 23, 42, 0.14)",
          "dark": "0 8px 24px rgba(0, 0, 0, 0.55)",
          "description": "Shadow of the tooltip."
        },
        {
          "name": "--pct-chart-unit-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "The unit written over a value axis."
        },
        {
          "name": "--pct-chart-zoom-gap",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Gap between the buttons of the zoom bar."
        }
      ],
      "texts": [
        "chartAllHidden",
        "chartBand",
        "chartDifference",
        "chartEarlier",
        "chartEmpty",
        "chartGap",
        "chartHigh",
        "chartKeys",
        "chartLater",
        "chartLegend",
        "chartLow",
        "chartMean",
        "chartNoCursor",
        "chartNow",
        "chartOff",
        "chartOn",
        "chartOpen",
        "chartOutliers",
        "chartPosition",
        "chartQuartile1",
        "chartQuartile3",
        "chartReference",
        "chartRemove",
        "chartSize",
        "chartSpan",
        "chartSum",
        "chartTime",
        "chartZoom",
        "chartZoomIn",
        "chartZoomOut",
        "chartZoomReset"
      ],
      "keyboard": "| key                       | effect                                                                                                     | test                                                                                                                     |\n| ------------------------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |\n| `Tab`                     | reaches the cursor's range, the chart's one stop; the picture is a named image and no stop                 | `apps/sandbox-e2e/src/chart.spec.ts` — \"the cursor is the chart's one stop, a range named by the chart\"                  |\n| `→` / `↑`                 | the next reading of any series, a column of pixels at least; with no cursor, the first in the window       | `apps/sandbox-e2e/src/chart.spec.ts` — \"the arrows walk the cursor from reading to reading, Home and End go to the ends\" |\n| `←` / `↓`                 | the reading before; with no cursor, the last in the window                                                 | `apps/sandbox-e2e/src/chart.spec.ts` — \"the arrows walk the cursor from reading to reading, Home and End go to the ends\" |\n| `Home` / `End`            | the first and the last reading in the window                                                               | `apps/sandbox-e2e/src/chart.spec.ts` — \"the arrows walk the cursor from reading to reading, Home and End go to the ends\" |\n| `Page Up` / `Page Down`   | the first reading a tenth of the window further, or the last that way                                      | `apps/sandbox-e2e/src/chart.spec.ts` — \"Page Up and Page Down move a tenth of the window\"                                |\n| `+` / `−`                 | zooms the window in and out, about the cursor or the middle                                                | `apps/sandbox-e2e/src/chart.spec.ts` — \"the keys zoom, pan and return the window\"                                        |\n| `Shift` + `←` / `→`       | moves the window a tenth earlier or later; the cursor stays                                                | `apps/sandbox-e2e/src/chart.spec.ts` — \"the keys zoom, pan and return the window\"                                        |\n| `Shift` + `↑` / `↓`       | with `zoom=\"xy\"`, moves the first value axis's window a tenth up or down                                   | `apps/sandbox-e2e/src/chart.spec.ts` — \"a zoom on both axes narrows the first value axis\"                                |\n| `0`                       | returns the window to `defaultWindow`, and the first value axis's to its data                              | `apps/sandbox-e2e/src/chart.spec.ts` — \"the keys zoom, pan and return the window\"                                        |\n| `Tab` in the legend       | each entry's toggle is a stop, and each remove button; `Enter` and `Space` press them                      | `apps/sandbox-e2e/src/chart.spec.ts` — \"the legend hides a series on a press and shows it on the next\"                   |\n| `←` / `→` on the zoom bar | move between its buttons, `Home` and `End` to the first and last; the bar is one stop                      | `apps/sandbox-e2e/src/chart.spec.ts` — \"the zoom bar is one stop whose buttons move the window\"                          |\n| `Enter` / `Space`         | selects the band of the bar under the cursor, and lets it go on the next; with no bar there the key passes | `apps/sandbox-e2e/src/chart.spec.ts` — \"a press selects a bar's band, and Enter at the cursor does the same\"             |\n| `Escape` / `Delete`       | clears the cursor; with none, `Escape` is left to the page (a dialog around the chart closes on it)        | `apps/sandbox-e2e/src/chart.spec.ts` — \"Escape and Delete clear the cursor, and Escape passes on when there is none\"     |",
      "decisions": [],
      "evidence": {
        "mutation": {
          "score": 91.93,
          "killed": 1561,
          "survived": 131,
          "mutants": 1698,
          "files": 1
        },
        "e2e": {
          "cases": 47,
          "spec": "apps/sandbox-e2e/src/chart.spec.ts"
        },
        "pairs": 53,
        "baselines": 2,
        "cost": {
          "elements": 48,
          "depth": 6,
          "listeners": 2,
          "renders": 1,
          "micros": 36127,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "datatable",
      "classes": [
        "PctDataTable"
      ],
      "role": "a table built from data",
      "category": "Data & status",
      "status": "released",
      "entrypoint": "@pacit/components/table",
      "selectors": [
        "pct-data-table",
        "pct-data-table[pctTableDetails]",
        "pct-data-table[pctTableGroups]",
        "pct-data-table[pctTableSelection]",
        "pct-data-table[pctTableWindow]",
        "pct-data-table[pctTablePage]",
        "pct-data-table[pctTableRemote]",
        "ng-template[pctCell]",
        "ng-template[pctHeaderCell]",
        "ng-template[pctEmpty]",
        "ng-template[pctDetails]"
      ],
      "harnesses": [
        "PctDataTableHarness"
      ],
      "docs": "/components/datatable",
      "summary": "A table drawn from your rows and a typed list of columns: sorting by one key or several, a quick filter over what the cells show, and the empty and loading states, in the look of the platform table.",
      "usage": {
        "code": "const col = pctColumnsOf<Rule>();\nreadonly columns = col.of([\n  col.key('name', { header: 'Name' }),\n  col.key('energy', { header: 'Energy', kind: { number: { unit: 'kWh' } } }),\n  col.key('enabled', { header: 'Enabled', kind: 'boolean', sort: false }),\n]);",
        "lang": "ts"
      },
      "examples": [
        {
          "key": "filtered",
          "title": "The quick filter: every word in some column of the row, searched in the text it shows.",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctField, PctText } from '@pacit/components/field';\nimport { PctDataTable, pctColumnsOf } from '@pacit/components/table';\n\ninterface Device {\n  readonly id: number;\n  readonly name: string;\n  readonly room: string;\n  readonly power: number;\n}\n\nconst col = pctColumnsOf<Device>();\n\n/** The quick filter: every word in some column of the row, searched in the text it shows. */\n@Component({\n  selector: 'demo-datatable-filtered',\n  imports: [PctDataTable, PctField, PctText],\n  template: `\n    <pct-field label=\"Filter devices\">\n      <input\n        pctText\n        type=\"search\"\n        [value]=\"question()\"\n        (input)=\"question.set($any($event.target).value)\"\n      />\n    </pct-field>\n    <pct-data-table\n      #table=\"pctDataTable\"\n      caption=\"Devices\"\n      [rows]=\"devices\"\n      [columns]=\"columns\"\n      [rowId]=\"byId\"\n      [(filter)]=\"question\"\n    />\n    <p>{{ table.matched() }} of {{ devices.length }}</p>\n  `,\n})\nexport class DatatableFilteredDemo {\n  readonly question = signal('');\n  readonly columns = col.of([\n    col.key('name', { header: 'Device' }),\n    col.key('room', { header: 'Room' }),\n    col.key('power', {\n      header: 'Power',\n      kind: { number: { unit: 'kW', fractionDigits: 1 } },\n    }),\n  ]);\n  readonly byId = (device: Device) => device.id;\n  readonly devices: readonly Device[] = [\n    { id: 1, name: 'Recuperator', room: 'Attic', power: 0.2 },\n    { id: 2, name: 'Inverter', room: 'Garage', power: 9.9 },\n    { id: 3, name: 'Heat pump', room: 'Garage', power: 2.4 },\n    { id: 4, name: 'Boiler', room: 'Kitchen', power: 1.8 },\n  ];\n}"
        }
      ],
      "api": [
        {
          "name": "PctDataTable",
          "kind": "component",
          "selector": "pct-data-table",
          "description": "A table built from data: typed columns, sorting by one key or several, a quick filter, and the empty and loading states, rendered as the first layer's <table pctTable> so the two look the same (0097).",
          "members": [
            {
              "name": "rows",
              "kind": "input",
              "required": false,
              "type": "readonly T[]",
              "default": "[]",
              "description": "The rows. They are read and never changed: the table filters and orders a copy.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "columns",
              "kind": "input",
              "required": true,
              "type": "PctColumns<T",
              "default": null,
              "description": "The columns, built with pctColumnsOf<T>() so every cell template is checked against their ids and their values' types.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "rowId",
              "kind": "input",
              "required": true,
              "type": "(row: T) => PctRowId",
              "default": null,
              "description": "What names a row across renders: @for tracks it, so a row keeps its element, its focus and its scroll position when the application hands the table a new list with the same ids. Unique across the rows.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "caption",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The table's name, drawn as its <caption>. A table needs a name: this, or ariaLabelledby.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "ariaLabelledby",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The id of an element that names the table, for a table whose name stands elsewhere on the page. It names the frame too when the frame scrolls.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The table's name in words, for a table with no caption and no element on the page to name it. It names the frame too when the frame scrolls.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "sort",
              "kind": "model",
              "required": false,
              "type": "readonly PctTableSort<keyof M & string>[]",
              "default": "[]",
              "description": "The order of the rows: a list of keys, the first deciding first. A press on a sortable header makes that column the only key, ascending, and turns it the other way on the next; with Shift held it adds the column as the last key, or turns it where it stands.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "unsortable",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "A third press on a descending column takes it out of the order, so the rows can go back to the order they came in. false by default, as the APG's sortable table toggles.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "clientSort",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "true",
              "description": "Whether the table orders the rows by sort itself. false for rows a server has already ordered: the headers still write sort, and the application asks the server.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "filter",
              "kind": "model",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The quick filter's question: every word in it must be found in some column of a row. A question, not a value: the table never writes it.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "clientFilter",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "true",
              "description": "Whether the table filters the rows by filter itself. false for rows a server has already filtered.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "filterWith",
              "kind": "input",
              "required": false,
              "type": "((row: T, question: string) => boolean) | null",
              "default": "null",
              "description": "A predicate in place of the quick filter's own, asked for every row with the question as it was typed, empty included, so it can answer other filters of the application's too.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "loading",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Rows are coming. The body is aria-busy, and with no row yet the table draws placeholder rows and says it is loading (0037).",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "locale",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The locale the values are written and the text ordered in; the application's LOCALE_ID when empty.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "now",
              "kind": "input",
              "required": false,
              "type": "PctInstant | null",
              "default": "null",
              "description": "The instant a relative column counts from. Unset, it is the clock's when the cells are written; an application that wants in 5 minutes to tick binds a clock of its own.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "stickyHeader",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "true",
              "description": "The header row stays at the top while the rows scroll under it. It needs a frame that scrolls, so it holds when the table is given a height, and true by default.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "striped",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Every other row on a tint, so a wide row is easier to follow across.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "rowNumbers",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "A leading column numbering the rows as they stand, from 1: the row a user names when they say \"row 12\". A cell of data, not the row's header: the row's name is its data.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "rowTone",
              "kind": "input",
              "required": false,
              "type": "((row: T) => PctTone | null) | null",
              "default": "null",
              "description": "The tone a row wears: a tint behind the whole row. Like a column's tone, it repeats what a cell of the row says and never says it alone.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "highlightMatches",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "true",
              "description": "The words the quick filter found are marked in the cells it read, so the eye sees why a row stayed. Only where the table's own filter decided: not with filterWith, nor with clientFilter off, where what matched is not the table's to say.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "highlightChanges",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "A cell whose value changed flashes for a moment, so a value that moved in a table of live readings is seen moving. A value is compared as itself, a date by its time: the clock moving a relative instant on is no change, and a value that is a new object at every poll flashes at every poll. The flash is a motion token's, so a user who asked for less motion sees none.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-data-table",
              "note": ""
            },
            {
              "attribute": "data-pct-sticky",
              "kind": "attribute",
              "bound": "stickyHeader() ? \"\" : null",
              "note": ""
            },
            {
              "attribute": "data-pct-striped",
              "kind": "attribute",
              "bound": "striped() ? \"\" : null",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctTableSort",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The column the rows stand sorted by, named by the key its header carries, and which way.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTable",
          "kind": "component",
          "selector": "table[pctTable]",
          "detail": null,
          "description": "A table the consumer writes, dressed by the library: the platform's <table>, its caption, header cells and cells, each element wearing a component of its own.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableCaption",
          "kind": "component",
          "selector": "caption[pctCaption]",
          "detail": null,
          "description": "The table's caption: its name, which a reader announces with the table, and the name of the frame around it when the frame scrolls.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableHeader",
          "kind": "component",
          "selector": "th[pctHeader]",
          "detail": null,
          "description": "A header cell. With a sortKey its content is a button that proposes sorting the rows by that column, and the header of the sorted column carries aria-sort.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableCell",
          "kind": "component",
          "selector": "td[pctCell]",
          "detail": null,
          "description": "A cell.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableFrame",
          "kind": "component",
          "selector": "pct-table-frame",
          "detail": null,
          "description": "The frame a table scrolls in when it is wider than the space it is given, or taller than a height it is given. While the table overflows, the frame is a focusable region named by the table's caption, so a keyboard can reach it and scroll it and a reader can say what it holds. While the table fits, the frame is nothing: a stop with nothing to scroll is a stop for nothing.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableVisible",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "The lines in the view, [start, end) among the lines drawn, and the ids of the rows among them, in order.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctScrollAlign",
          "kind": "type",
          "selector": null,
          "detail": "'start' | 'center' | 'end' | 'nearest'",
          "description": "Where a row the table scrolls to stands in the view: at its top, its centre, its bottom, or wherever is nearest from where the view is — the default.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctScrollToOptions",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "How scrollTo scrolls.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableFeature",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What a directive on the table adds to it, given once from its constructor: a hard list, one field per feature, so the table's own directives grow it and nothing else does (0097).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctDataTable",
          "kind": "component",
          "selector": "pct-data-table",
          "detail": null,
          "description": "A table built from data: typed columns, sorting by one key or several, a quick filter, and the empty and loading states, rendered as the first layer's <table pctTable> so the two look the same (0097).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctCellContext",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What a cell template is handed.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctCellTemplate",
          "kind": "directive",
          "selector": "ng-template[pctCell]",
          "detail": null,
          "description": "A cell the consumer draws for one column, or for several that share a look.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctHeaderCellContext",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What a header template is handed.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctHeaderCellTemplate",
          "kind": "directive",
          "selector": "ng-template[pctHeaderCell]",
          "detail": null,
          "description": "What a header shows in place of the column's header text.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctEmptyContext",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What the empty-state template is handed.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctEmptyTemplate",
          "kind": "directive",
          "selector": "ng-template[pctEmpty]",
          "detail": null,
          "description": "What the table shows beside itself when it has no row to show, in place of its sentence.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctDetailsContext",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What a details template is handed.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctDetailsTemplate",
          "kind": "directive",
          "selector": "ng-template[pctDetails]",
          "detail": null,
          "description": "What stands in a row's details, below the values of the columns a narrow table hid: a chart of the row's history, a form, anything the row has room for only when it is open. The row that opens and the button that opens it are the table's (0097).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableDetails",
          "kind": "directive",
          "selector": "pct-data-table[pctTableDetails]",
          "detail": null,
          "description": "The rows whose details are open, as the application's state: [(details)] binds the list of their ids, so a row opened in one view is open in the next, and disabled takes the disclosure away.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableGroups",
          "kind": "directive",
          "selector": "pct-data-table[pctTableGroups]",
          "detail": null,
          "description": "The rows in groups by the values of one column or several, each group a body of the table headed by a row: its label, how many rows it holds, the sums of the columns that sum up, and a button that collapses it (0097, D8).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctSelectionMode",
          "kind": "type",
          "selector": null,
          "detail": "'single' | 'multiple'",
          "description": "How many rows a selection holds: one, picked with a radio, or any, with a checkbox each and one in the header over all the rows the filter left.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableSelection",
          "kind": "directive",
          "selector": "pct-data-table[pctTableSelection]",
          "detail": null,
          "description": "The rows picked, as a form control: [(value)] binds the ids of the rows picked, and [formField] makes the selection a field like any other — its errors, its warnings and its hint stand under the table, and aria-describedby on the <table> names the one shown.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableSelectionParts",
          "kind": "component",
          "selector": null,
          "detail": null,
          "description": "The selection's parts: templates the table draws in the places it keeps for them, and the handlers of the controls in them. Declared here, rendered there: the bindings are read in this component, the elements stand in the table. pctTableSelection makes it, so it has no selector: there is no tag to write.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableWindow",
          "kind": "directive",
          "selector": "pct-data-table[pctTableWindow]",
          "detail": null,
          "description": "Draws only the lines in the table's view, and the space of the rest: a list of a hundred thousand rows lays out the forty a frame shows (0098).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTablePage",
          "kind": "directive",
          "selector": "pct-data-table[pctTablePage]",
          "detail": null,
          "description": "The rows the table is given are a page of more: rowOffset of them stand before it and rowTotal there are in all, so a reader is told \"row 43 of 1,001\", not \"row 3 of 21\".",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableRequest",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What a remote source is asked for: a block of rows, [from, to), in the order and under the question the table holds.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableAnswer",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What a remote source answers: the rows of the block asked for, and how many rows there are in all under the question.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableLoad",
          "kind": "type",
          "selector": null,
          "detail": "( request: PctTableRequest<Id>, signal: AbortSignal, ) => Promise<PctTableAnswer<T>>",
          "description": "Loads a block of rows. signal aborts when the table no longer needs the block: the order or the question changed, or the view left it before it came.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableRemote",
          "kind": "directive",
          "selector": "pct-data-table[pctTableRemote]",
          "detail": null,
          "description": "The rows come from a remote source a block at a time, as the view reaches them: one function, load, asked for the blocks of the lines the window draws and half a block on each side, each block once (0097).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableRemoteParts",
          "kind": "component",
          "selector": null,
          "detail": null,
          "description": "The parts a remote source's failure is drawn with: the sentence and the retry of the block, in the first hole drawn of it or where an empty table says why. Declared here, drawn by the table, so a table that loads nothing from a server carries none of it (0097). pctTableRemote makes it, so it has no selector: there is no tag to write.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctFilterContext",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What a filter template is handed: the state the column holds, and how to change it.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctFilterTemplate",
          "kind": "directive",
          "selector": "ng-template[pctFilter]",
          "detail": null,
          "description": "A column's filter the application draws: the controls of its own state, in the column's panel and in the row of filters, decided by the column's filterBy. The table keeps the state and writes it into columnFilters as { own: state }; what the state is, is the application's.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableColumnFilters",
          "kind": "directive",
          "selector": "pct-data-table[pctTableColumnFilters]",
          "detail": null,
          "description": "Each column's own filter, in a panel its header's button opens, and in a row of filters under the headers when filterRow is on (0097, D5). The control follows the column: a text field for its words, two number fields for a range, two date fields for the days a column of instants falls on, a multiple select of the values the rows hold, or the application's own template, with a button that clears it.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctTableFilterParts",
          "kind": "component",
          "selector": null,
          "detail": null,
          "description": "The filters' parts: templates the table draws in its places — a header's button and its panel, a column's cell in the row of filters — and the handlers of the controls in them. Declared here, rendered there: the bindings are read in this component, the elements stand in the table. pctTableColumnFilters makes it, so it has no selector: there is no tag to write.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        }
      ],
      "parts": [
        {
          "name": "details",
          "description": "the row of details below a row, the full width of the table"
        },
        {
          "name": "disclosure",
          "description": "the button that opens a row's details, in the leading column"
        },
        {
          "name": "group",
          "description": "a group's heading, the first row of its body"
        },
        {
          "name": "hole",
          "description": "a row a remote source has not delivered: its cells loading, or its block failed"
        },
        {
          "name": "mark",
          "description": "a word the quick filter found, in a cell it read by its text"
        },
        {
          "name": "message",
          "description": "the sentence beside an empty table, outside the table"
        },
        {
          "name": "place",
          "description": "the place and the way of a sort key after the first, drawn for the eye"
        },
        {
          "name": "placeholder",
          "description": "a row of skeletons standing for rows that are still coming, hidden from readers"
        },
        {
          "name": "filter",
          "description": "a column's filter button beside its header's label, which opens its panel"
        },
        {
          "name": "icon",
          "description": "its chevron"
        },
        {
          "name": "toggle",
          "description": "the first layer's button in a sortable header — see the table's card"
        },
        {
          "name": "retry",
          "description": "the button in the first hole drawn of a failed block, which asks for it again"
        },
        {
          "name": "error",
          "description": "the selection's error under the table, once touched"
        },
        {
          "name": "hint",
          "description": "the selection's hint under the table"
        },
        {
          "name": "select",
          "description": "a row's checkbox, or its radio in single mode, in the leading column"
        },
        {
          "name": "select-all",
          "description": "the header's checkbox, over the rows the filter left"
        },
        {
          "name": "warning",
          "description": "the selection's warning under the table, with its glyph"
        }
      ],
      "tokens": [
        {
          "name": "--pct-table-bg-changed",
          "type": "color",
          "ref": "{pct.warning-200}",
          "light": "#fde68a",
          "dark": "#b45309",
          "description": "Background a data table's cell flashes in when its value changes."
        },
        {
          "name": "--pct-table-bg-danger",
          "type": "color",
          "ref": "{pct.danger-100}",
          "light": "#fee2e2",
          "dark": "#991b1b",
          "description": "Background of a data table's row or cell in the danger tone."
        },
        {
          "name": "--pct-table-bg-hover",
          "type": "color",
          "ref": "{pct.primary-200}",
          "light": "#bfdbfe",
          "dark": "#1d4ed8",
          "description": "Background of a data table's row under the pointer."
        },
        {
          "name": "--pct-table-bg-info",
          "type": "color",
          "ref": "{pct.info-100}",
          "light": "#dbeafe",
          "dark": "#1e40af",
          "description": "Background of a data table's row or cell in the info tone."
        },
        {
          "name": "--pct-table-bg-selected",
          "type": "color",
          "ref": "{pct.primary-100}",
          "light": "#dbeafe",
          "dark": "#1e40af",
          "description": "Background of a data table's picked row."
        },
        {
          "name": "--pct-table-bg-striped",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of every other row of a striped data table."
        },
        {
          "name": "--pct-table-bg-success",
          "type": "color",
          "ref": "{pct.success-100}",
          "light": "#dcfce7",
          "dark": "#166534",
          "description": "Background of a data table's row or cell in the success tone."
        },
        {
          "name": "--pct-table-bg-warning",
          "type": "color",
          "ref": "{pct.warning-100}",
          "light": "#fef3c7",
          "dark": "#92400e",
          "description": "Background of a data table's row or cell in the warning tone."
        },
        {
          "name": "--pct-table-border",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Line under a row."
        },
        {
          "name": "--pct-table-caption-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the caption."
        },
        {
          "name": "--pct-table-caption-font-weight",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of the caption."
        },
        {
          "name": "--pct-table-caption-gap",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Space between the caption and the table."
        },
        {
          "name": "--pct-table-column-max-width",
          "type": "dimension",
          "ref": null,
          "light": "20rem",
          "dark": "20rem",
          "description": "Maximum width of a data table's column whose cells are cut to one line."
        },
        {
          "name": "--pct-table-details-bg",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of a data table's details row."
        },
        {
          "name": "--pct-table-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the cells."
        },
        {
          "name": "--pct-table-fg-danger",
          "type": "color",
          "ref": "{pct.on-danger-100}",
          "light": "#991b1b",
          "dark": "#fee2e2",
          "description": "Text colour of a data table's row or cell in the danger tone."
        },
        {
          "name": "--pct-table-fg-info",
          "type": "color",
          "ref": "{pct.on-info-100}",
          "light": "#1d4ed8",
          "dark": "#bfdbfe",
          "description": "Text colour of a data table's row or cell in the info tone."
        },
        {
          "name": "--pct-table-fg-muted",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the numbers of a data table's rows."
        },
        {
          "name": "--pct-table-fg-selected",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of a data table's picked row."
        },
        {
          "name": "--pct-table-fg-success",
          "type": "color",
          "ref": "{pct.on-success-100}",
          "light": "#166534",
          "dark": "#dcfce7",
          "description": "Text colour of a data table's row or cell in the success tone."
        },
        {
          "name": "--pct-table-fg-warning",
          "type": "color",
          "ref": "{pct.on-warning-100}",
          "light": "#92400e",
          "dark": "#fef3c7",
          "description": "Text colour of a data table's row or cell in the warning tone."
        },
        {
          "name": "--pct-table-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of the table."
        },
        {
          "name": "--pct-table-group-bg",
          "type": "color",
          "ref": "{pct.primary-100}",
          "light": "#dbeafe",
          "dark": "#1e40af",
          "description": "Background of a data table's group heading."
        },
        {
          "name": "--pct-table-group-fg",
          "type": "color",
          "ref": "{pct.on-primary-100}",
          "light": "#1d4ed8",
          "dark": "#bfdbfe",
          "description": "Text colour of a data table's group heading."
        },
        {
          "name": "--pct-table-heading-bg",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of a header cell."
        },
        {
          "name": "--pct-table-heading-border",
          "type": "color",
          "ref": "{pct.border-strong}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Line under the header row."
        },
        {
          "name": "--pct-table-heading-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of a header cell."
        },
        {
          "name": "--pct-table-heading-font-weight",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of a header cell."
        },
        {
          "name": "--pct-table-hint-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the hint under a data table that picks rows."
        },
        {
          "name": "--pct-table-hint-fg-invalid",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Text colour of the error under a data table that picks rows."
        },
        {
          "name": "--pct-table-hint-fg-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Text colour of the warning under a data table that picks rows."
        },
        {
          "name": "--pct-table-icon-fg-muted",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Colour of the chevron a sortable header shows as a hint."
        },
        {
          "name": "--pct-table-indent",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "How far a data table indents each level of its groups."
        },
        {
          "name": "--pct-table-mark-bg",
          "type": "color",
          "ref": "{pct.warning-200}",
          "light": "#fde68a",
          "dark": "#b45309",
          "description": "Background of a word the quick filter found."
        },
        {
          "name": "--pct-table-mark-fg",
          "type": "color",
          "ref": "{pct.on-warning-100}",
          "light": "#92400e",
          "dark": "#fef3c7",
          "description": "Text colour of a word the quick filter found."
        },
        {
          "name": "--pct-table-padding-x",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of a cell and a header cell."
        },
        {
          "name": "--pct-table-padding-y",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Vertical padding of a cell and a header cell."
        },
        {
          "name": "--pct-table-toggle-gap",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Gap between a sortable header's label and its chevron."
        },
        {
          "name": "--pct-table-toggle-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Touch-target floor of a sortable header."
        }
      ],
      "texts": [
        "fieldWarning",
        "tableClear",
        "tableDetails",
        "tableDetailsClose",
        "tableDetailsOpen",
        "tableEmpty",
        "tableFilter",
        "tableFiltered",
        "tableFrom",
        "tableGroupCount",
        "tableGroupEmpty",
        "tableGroupWithin",
        "tableLoadFailed",
        "tableLoading",
        "tableNo",
        "tableNoMatches",
        "tableRetry",
        "tableRowNumber",
        "tableSelectAll",
        "tableSelectRow",
        "tableSortAscending",
        "tableSortDescending",
        "tableSortLater",
        "tableTo",
        "tableTotal",
        "tableYes"
      ],
      "keyboard": "| key                                | effect                                                                 | test                                                                                                                                                                                                                                                |\n| ---------------------------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `Tab` / `Shift+Tab`                | moves between the sort buttons and the controls in the cells           | `apps/sandbox-e2e/src/datatable.spec.ts` — \"Tab moves from the sort buttons into the controls in the cells\", and `Shift+Tab` in the next                                                                                                            |\n| `Enter` / `Space`                  | sorts by the column as its only key, or turns the first key            | `apps/sandbox-e2e/src/datatable.spec.ts` — \"the keyboard sorts by one key, Space turns it, and Shift+Enter adds a second marked by its place\"                                                                                                       |\n| `Shift+Enter`                      | adds the column as the next key, or turns it where it stands           | the same case                                                                                                                                                                                                                                       |\n| `Enter` / `Space` on a disclosure  | opens the row's details, or closes them                                | `apps/sandbox-e2e/src/datatable.spec.ts` — \"a narrow table hides the columns of the highest priority, and its details list them\"; Tab through them in \"Tab goes from a row's disclosure to its controls, into its details, then on to the next row\" |\n| `Space` on a row's checkbox        | picks the row, or drops it                                             | `apps/sandbox-e2e/src/datatable.spec.ts` — \"the keyboard picks a row, Shift with a press picks the rows between, and the header reads clear, mixed and checked\"                                                                                     |\n| `Shift` with a press on a checkbox | picks, or drops, every row drawn between it and the row pressed before | the same case                                                                                                                                                                                                                                       |\n| `Space` on the header's checkbox   | picks every row the filter left, or drops them when all are picked     | the same case                                                                                                                                                                                                                                       |\n| `↑` / `↓` among the radios         | moves the one pick, the platform's radio group                         | `apps/sandbox-e2e/src/datatable.spec.ts` — \"one rule to copy: radios in one group, the arrows move the pick\"                                                                                                                                        |",
      "decisions": [],
      "evidence": {
        "mutation": {
          "score": 98.1,
          "killed": 1086,
          "survived": 17,
          "mutants": 1107,
          "files": 1
        },
        "e2e": {
          "cases": 46,
          "spec": "apps/sandbox-e2e/src/datatable.spec.ts"
        },
        "pairs": 0,
        "baselines": 2,
        "cost": {
          "elements": 34,
          "depth": 10,
          "listeners": 11,
          "renders": 1,
          "micros": 28773,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "accordion",
      "classes": [
        "PctAccordion"
      ],
      "role": "a stack of sections a user opens and closes",
      "category": "Data & status",
      "status": "released",
      "entrypoint": "@pacit/components/accordion",
      "selectors": [
        "pct-accordion",
        "pct-accordion-item"
      ],
      "harnesses": [
        "PctAccordionHarness",
        "PctAccordionItemHarness"
      ],
      "docs": "/components/accordion",
      "summary": "A stack of sections the reader opens and closes — one at a time, or as many as they like.",
      "usage": {
        "code": "<pct-accordion>\n  <pct-accordion-item label=\"What ships in the package?\"> Standalone components, design tokens and schematics. </pct-accordion-item>\n</pct-accordion>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "exclusive",
          "title": "One at a time",
          "code": "import { Component } from '@angular/core';\nimport { PctAccordion, PctAccordionItem } from '@pacit/components/accordion';\n\n/**\n * One at a time\n *\n * `exclusive` closes the open section when another opens — a FAQ reads better that way.\n * Without it every section keeps its own state, which is what a settings page wants.\n * `headingLevel` sets the heading the buttons sit in, so the outline stays honest.\n */\n@Component({\n  selector: 'demo-accordion-exclusive',\n  imports: [PctAccordion, PctAccordionItem],\n  styles: ':host { display: block; inline-size: min(100%, 36rem); }',\n  template: `\n    <pct-accordion exclusive [headingLevel]=\"4\">\n      <pct-accordion-item label=\"Do I need zone.js?\">\n        No — the library is zoneless from the first line.\n      </pct-accordion-item>\n      <pct-accordion-item label=\"Does it render on the server?\">\n        Every component renders on the server and hydrates without a mismatch.\n      </pct-accordion-item>\n      <pct-accordion-item label=\"Can a screen reader use it?\">\n        Every promise about that is a test in three engines.\n      </pct-accordion-item>\n    </pct-accordion>\n  `,\n})\nexport class AccordionExclusiveDemo {}"
        }
      ],
      "api": [
        {
          "name": "PctAccordion",
          "kind": "component",
          "selector": "pct-accordion",
          "description": "An accordion: a stack of sections a user opens and closes, each with a heading of its own.",
          "members": [
            {
              "name": "exclusive",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Whether opening one section closes the rest.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "headingLevel",
              "kind": "input",
              "required": false,
              "type": "PctAccordionHeadingLevel",
              "default": "3",
              "description": "The level the items' titles are drawn at — <h3> unless told otherwise, which is the level a section of a page under one <h1> and one <h2> usually wants.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-accordion",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctAccordionApi",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What an item needs to know about the group it stands in: the name that makes the group exclusive, and the level its title is drawn at. Declared HERE, beside the group, so that the item can inject it while importing the group — the other direction would close the import cycle. It is the tabs' channel one component over, and for the same reason.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PCT_ACCORDION",
          "kind": "token",
          "selector": null,
          "detail": null,
          "description": "The channel through which a group offers itself to the items below it.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctAccordion",
          "kind": "component",
          "selector": "pct-accordion",
          "detail": null,
          "description": "An accordion: a stack of sections a user opens and closes, each with a heading of its own.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctAccordionItem",
          "kind": "component",
          "selector": "pct-accordion-item",
          "detail": null,
          "description": "One section of a <pct-accordion>: a <details> with a <summary>, a heading inside the summary, and the consumer's content below it.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctAccordionHeadingLevel",
          "kind": "type",
          "selector": null,
          "detail": "2 | 3 | 4 | 5 | 6",
          "description": "The heading level an item's title is drawn at — a real <h2>…<h6>, because the whole reason the title is a heading is that a screen-reader user jumps between sections with the H key, and that navigation reads the document's outline rather than an attribute.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "heading",
          "description": "the button that opens and closes the section"
        },
        {
          "name": "item",
          "description": "one section: its heading and its panel"
        },
        {
          "name": "marker",
          "description": "the open-or-closed sign beside the heading"
        },
        {
          "name": "panel",
          "description": "the content shown while the section is open"
        }
      ],
      "tokens": [
        {
          "name": "--pct-accordion-heading-bg-hover",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the heading on hover."
        },
        {
          "name": "--pct-accordion-heading-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the heading."
        },
        {
          "name": "--pct-accordion-heading-fg-disabled",
          "type": "color",
          "ref": "{pct.text-disabled}",
          "light": "#64748b",
          "dark": "#64748b",
          "description": "Text colour of the heading while disabled."
        },
        {
          "name": "--pct-accordion-heading-fg-hover",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the heading on hover."
        },
        {
          "name": "--pct-accordion-heading-font-weight",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of the heading."
        },
        {
          "name": "--pct-accordion-heading-gap",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Gap between the heading's text and its marker."
        },
        {
          "name": "--pct-accordion-heading-padding-x",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Horizontal padding of the heading."
        },
        {
          "name": "--pct-accordion-heading-padding-y",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Vertical padding of the heading."
        },
        {
          "name": "--pct-accordion-heading-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Touch-target floor of the heading."
        },
        {
          "name": "--pct-accordion-item-border",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge colour between items."
        },
        {
          "name": "--pct-accordion-marker-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Colour of the open-or-closed marker beside the heading."
        },
        {
          "name": "--pct-accordion-panel-padding-y",
          "type": "dimension",
          "ref": "{pct.space.4}",
          "light": "12px",
          "dark": "12px",
          "description": "Vertical padding of the panel."
        }
      ],
      "texts": [],
      "keyboard": "| key                       | effect                                                         | test                                     |\n| ------------------------- | -------------------------------------------------------------- | ---------------------------------------- |\n| `Tab`                     | one stop per heading — every section is in the page's sequence | `apps/sandbox-e2e/src/accordion.spec.ts` |\n| `Enter` / `Space`         | opens or closes the focused section — the **platform's**       | `apps/sandbox-e2e/src/accordion.spec.ts` |\n| `Enter` on a disabled one | nothing, because the click it arrives as is cancelled          | `apps/sandbox-e2e/src/accordion.spec.ts` |\n\n**There is no arrow-key walk, and that is the pattern's own answer.** The APG lists `Up`/`Down`\nbetween headers as _optional_, and lists \"all focusable elements in the accordion are in the\npage Tab sequence\" as required — which the platform already does. A document-level key map\nwould be this library taking `ArrowDown` away from the page\n([`req-api-platform`](../requirements/api.md#req-api-platform)).",
      "decisions": [
        {
          "id": "0046",
          "title": "A disclosure is the platform's, and so is the group it belongs to"
        },
        {
          "id": "0039",
          "title": "A state the platform publishes is not ours to write"
        },
        {
          "id": "0045",
          "title": "A panel nobody chose is still text in the document"
        },
        {
          "id": "0011",
          "title": "Icons through a template and PCT_ICONS"
        },
        {
          "id": "0013",
          "title": "No headless core / skin split"
        }
      ],
      "evidence": {
        "mutation": {
          "score": 75,
          "killed": 3,
          "survived": 1,
          "mutants": 4,
          "files": 1
        },
        "e2e": {
          "cases": 12,
          "spec": "apps/sandbox-e2e/src/accordion.spec.ts"
        },
        "pairs": 5,
        "baselines": 1,
        "cost": {
          "elements": 25,
          "depth": 7,
          "listeners": 8,
          "renders": 1,
          "micros": 11647,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "tree",
      "classes": [
        "PctTree"
      ],
      "role": "a walk the platform does not have",
      "category": "Data & status",
      "status": "released",
      "entrypoint": "@pacit/components/tree",
      "selectors": [
        "pct-tree",
        "pct-tree-item"
      ],
      "harnesses": [
        "PctTreeHarness",
        "PctTreeItemHarness"
      ],
      "docs": "/components/tree",
      "summary": "A tree the reader walks with the keyboard: branches that open, leaves that get chosen.",
      "usage": {
        "code": "<pct-tree ariaLabel=\"Files\" [(selected)]=\"chosen\">\n  <pct-tree-item value=\"src\" [expanded]=\"true\">\n    src\n    <pct-tree-item value=\"src/app.ts\">app.ts</pct-tree-item>\n  </pct-tree-item>\n</pct-tree>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "nested",
          "title": "A deep branch, opened",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctTree, PctTreeItem } from '@pacit/components/tree';\n\n/**\n * A deep branch, opened\n *\n * Every `pct-tree-item` can hold more items; `expanded` opens a branch from the start, and\n * `selected` is a two-way value — set it, and the tree shows the way to that node.\n */\n@Component({\n  selector: 'demo-tree-nested',\n  imports: [PctTree, PctTreeItem],\n  styles: ':host { display: grid; gap: 12px; inline-size: min(100%, 20rem); }',\n  template: `\n    <pct-tree ariaLabel=\"Repository\" [(selected)]=\"chosen\">\n      <pct-tree-item value=\"apps\" [expanded]=\"true\">\n        apps\n        <pct-tree-item value=\"apps/docs\" [expanded]=\"true\">\n          docs\n          <pct-tree-item value=\"apps/docs/src\">src</pct-tree-item>\n        </pct-tree-item>\n        <pct-tree-item value=\"apps/sandbox\">sandbox</pct-tree-item>\n      </pct-tree-item>\n      <pct-tree-item value=\"libs\">\n        libs\n        <pct-tree-item value=\"libs/components\">components</pct-tree-item>\n        <pct-tree-item value=\"libs/tokens\">tokens</pct-tree-item>\n      </pct-tree-item>\n    </pct-tree>\n    <p>Selected: {{ chosen() ?? 'nothing yet' }}</p>\n  `,\n})\nexport class TreeNestedDemo {\n  readonly chosen = signal<string | null>('apps/docs/src');\n}"
        }
      ],
      "api": [
        {
          "name": "PctTree",
          "kind": "component",
          "selector": "pct-tree",
          "description": "A tree: a hierarchy the user walks — a file explorer, a nested outline.",
          "members": [
            {
              "name": "selected",
              "kind": "model",
              "required": false,
              "type": "string | null",
              "default": "null",
              "description": "The chosen item's value — the one thing the tree owns (the pagination's ownership at a hierarchy). A model, because both directions are ordinary: the application preselects, and Enter, Space or a click writes back. null is \"nothing chosen\".",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name of the tree. Optional, the chips' reasoning: a library default would have to guess what hierarchy this is, and two trees on one page are told apart here.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-tree",
              "note": ""
            },
            {
              "attribute": "role",
              "kind": "static",
              "bound": "tree",
              "note": ""
            },
            {
              "attribute": "aria-label",
              "kind": "attribute",
              "bound": "ariaLabel() || null",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctTree",
          "kind": "component",
          "selector": "pct-tree",
          "detail": null,
          "description": "A tree: a hierarchy the user walks — a file explorer, a nested outline.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctTreeItem",
          "kind": "component",
          "selector": "pct-tree-item",
          "detail": null,
          "description": "One node: role=\"treeitem\", its own text as the label, and any nested pct-tree-item elements pulled into the branch's role=\"group\" — the consumer writes the tree as the tree, and the two-slot projection does the sorting.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "arrow",
          "description": "the toggle that expands a branch"
        },
        {
          "name": "label",
          "description": "a node's text, the row that is selected"
        }
      ],
      "tokens": [
        {
          "name": "--pct-tree-arrow-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the arrow."
        },
        {
          "name": "--pct-tree-arrow-size",
          "type": "dimension",
          "ref": null,
          "light": "12px",
          "dark": "12px",
          "description": "Size of the arrow."
        },
        {
          "name": "--pct-tree-gap",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Gap between nodes."
        },
        {
          "name": "--pct-tree-indent",
          "type": "dimension",
          "ref": "{pct.space.5}",
          "light": "16px",
          "dark": "16px",
          "description": "Indent of the tree per level."
        },
        {
          "name": "--pct-tree-label-bg-hover",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the label on hover."
        },
        {
          "name": "--pct-tree-label-bg-selected",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Background of the label when selected."
        },
        {
          "name": "--pct-tree-label-fg-selected",
          "type": "color",
          "ref": "{pct.on-primary}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Text colour of the label when selected."
        },
        {
          "name": "--pct-tree-label-padding-x",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Horizontal padding of the label."
        },
        {
          "name": "--pct-tree-label-padding-y",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Vertical padding of the label."
        },
        {
          "name": "--pct-tree-label-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the label."
        },
        {
          "name": "--pct-tree-label-target-min",
          "type": "dimension",
          "ref": "{pct.target.min}",
          "light": "24px",
          "dark": "24px",
          "description": "Touch-target floor of the label."
        }
      ],
      "texts": [],
      "keyboard": "| key                     | effect                                            | test                                                                                  |\n| ----------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| `Tab` / `Shift+Tab`     | enters at the roving item / leaves the whole tree | `apps/sandbox-e2e/src/tree.spec.ts` — \"one tab stop\"                                  |\n| `ArrowDown` / `ArrowUp` | next / previous visible row                       | `tree.spec.ts` — \"the arrows walk visible rows\" (and unit, `tree.spec.ts` in the lib) |\n| `ArrowRight` (LTR)      | opens a closed branch; steps into an open one     | the same case; the RTL swap: `apps/sandbox-e2e/src/rtl.spec.ts`                       |\n| `ArrowLeft` (LTR)       | closes an open branch; otherwise climbs           | the same two cases                                                                    |\n| `Home` / `End`          | first / last visible row                          | `tree.spec.ts` — both readings                                                        |\n| `Enter` / `Space`       | chooses the row under focus                       | `tree.spec.ts` — \"Enter chooses\"; Space in the unit suite                             |",
      "decisions": [],
      "evidence": {
        "mutation": {
          "score": 93.28,
          "killed": 111,
          "survived": 7,
          "mutants": 119,
          "files": 1
        },
        "e2e": {
          "cases": 7,
          "spec": "apps/sandbox-e2e/src/tree.spec.ts"
        },
        "pairs": 4,
        "baselines": 2,
        "cost": {
          "elements": 21,
          "depth": 7,
          "listeners": 10,
          "renders": 1,
          "micros": 8226,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "stepper",
      "classes": [
        "PctStepper"
      ],
      "role": "a map of a journey the application steers",
      "category": "Data & status",
      "status": "released",
      "entrypoint": "@pacit/components/stepper",
      "selectors": [
        "pct-stepper",
        "pct-step"
      ],
      "harnesses": [
        "PctStepperHarness",
        "PctStepHarness"
      ],
      "docs": "/components/stepper",
      "summary": "A map of a journey: the steps behind, the step now, and the steps still to come.",
      "usage": {
        "code": "<pct-stepper [step]=\"2\" ariaLabel=\"Checkout\">\n  <pct-step>Cart</pct-step>\n  <pct-step>Delivery</pct-step>\n  <pct-step>Payment</pct-step>\n</pct-stepper>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "steered",
          "title": "A journey the application steers",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctButton } from '@pacit/components/button';\nimport { PctStep, PctStepper } from '@pacit/components/stepper';\n\n/**\n * A journey the application steers\n *\n * The stepper is a map, not a wizard: `step` is one-indexed and the application moves it.\n * Nothing here is clickable on purpose — a step you could jump to would be navigation, and\n * that belongs to the page's own links.\n */\n@Component({\n  selector: 'demo-stepper-steered',\n  imports: [PctButton, PctStep, PctStepper],\n  styles: ':host { display: grid; gap: 16px; }',\n  template: `\n    <pct-stepper [step]=\"step()\" ariaLabel=\"Onboarding\">\n      <pct-step>Account</pct-step>\n      <pct-step>Workspace</pct-step>\n      <pct-step>Invite</pct-step>\n    </pct-stepper>\n    <div style=\"display: flex; gap: 8px\">\n      <button\n        pctButton\n        variant=\"outline\"\n        size=\"sm\"\n        [disabled]=\"step() <= 1\"\n        (click)=\"step.update((s) => s - 1)\"\n      >\n        Back\n      </button>\n      <button\n        pctButton\n        size=\"sm\"\n        [disabled]=\"step() >= 3\"\n        (click)=\"step.update((s) => s + 1)\"\n      >\n        Next\n      </button>\n    </div>\n  `,\n})\nexport class StepperSteeredDemo {\n  readonly step = signal(2);\n}"
        }
      ],
      "api": [
        {
          "name": "PctStepper",
          "kind": "component",
          "selector": "pct-stepper",
          "description": "A stepper: the map of a multi-step journey — \"step 2 of 4\" above a checkout.",
          "members": [
            {
              "name": "step",
              "kind": "input",
              "required": true,
              "type": "number",
              "default": null,
              "description": "Where the journey stands, 1-based — \"step 2 of 4\" is what the user reads, and the same number is the current marker's visible ordinal (the pagination's argument for its page). Below 1 every step is upcoming; past the count every step is done — both are honest sentences about a journey not yet started or already finished, so nothing is clamped and nothing written back.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name of the list. Optional, the chips' reasoning: a list is allowed to be nameless, and a library default would have to guess what journey this is — two maps on one page (\"Checkout\", \"Onboarding\") are told apart here.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-stepper",
              "note": ""
            },
            {
              "attribute": "role",
              "kind": "static",
              "bound": "list",
              "note": "Static for the chips' measured reason: an empty <code>list</code> raises nothing in any engine's audit (lesson-138), so a row with no steps yet needs no machinery."
            },
            {
              "attribute": "aria-label",
              "kind": "attribute",
              "bound": "ariaLabel() || null",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctStepState",
          "kind": "type",
          "selector": null,
          "detail": "'done' | 'current' | 'upcoming'",
          "description": "What a step is at this moment of the journey — computed, never set.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctStepper",
          "kind": "component",
          "selector": "pct-stepper",
          "detail": null,
          "description": "A stepper: the map of a multi-step journey — \"step 2 of 4\" above a checkout.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctStep",
          "kind": "component",
          "selector": "pct-step",
          "detail": null,
          "description": "One step of the map: role=\"listitem\", the connector and the marker drawn before the projected label, and the state computed from the row's one number — never set here.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "marker",
          "description": "the numbered circle of a step"
        },
        {
          "name": "track",
          "description": "the line between two markers"
        }
      ],
      "tokens": [
        {
          "name": "--pct-stepper-gap",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Gap between steps."
        },
        {
          "name": "--pct-stepper-label-fg-muted",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the label when muted."
        },
        {
          "name": "--pct-stepper-label-font-weight-current",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of the label for the current one."
        },
        {
          "name": "--pct-stepper-marker-bg",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of a step's marker."
        },
        {
          "name": "--pct-stepper-marker-bg-current",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Background of a step's marker for the current one."
        },
        {
          "name": "--pct-stepper-marker-bg-done",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Background of a step's marker for a done one."
        },
        {
          "name": "--pct-stepper-marker-border",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge colour of a step's marker."
        },
        {
          "name": "--pct-stepper-marker-border-current",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge colour of a step's marker for the current one."
        },
        {
          "name": "--pct-stepper-marker-border-done",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge colour of a step's marker for a done one."
        },
        {
          "name": "--pct-stepper-marker-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of a step's marker."
        },
        {
          "name": "--pct-stepper-marker-fg-current",
          "type": "color",
          "ref": "{pct.on-primary}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Text colour of a step's marker for the current one."
        },
        {
          "name": "--pct-stepper-marker-fg-done",
          "type": "color",
          "ref": "{pct.on-primary}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Text colour of a step's marker for a done one."
        },
        {
          "name": "--pct-stepper-marker-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of a step's marker."
        },
        {
          "name": "--pct-stepper-marker-radius",
          "type": "dimension",
          "ref": null,
          "light": "999px",
          "dark": "999px",
          "description": "Corner radius of a step's marker."
        },
        {
          "name": "--pct-stepper-marker-size",
          "type": "dimension",
          "ref": null,
          "light": "28px",
          "dark": "28px",
          "description": "Size of a step's marker."
        },
        {
          "name": "--pct-stepper-track-bg",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Background of the track."
        },
        {
          "name": "--pct-stepper-track-size",
          "type": "dimension",
          "ref": null,
          "light": "24px",
          "dark": "24px",
          "description": "Size of the track."
        }
      ],
      "texts": [
        "stepDone"
      ],
      "keyboard": "| key | effect | test |\n| --- | ------ | ---- |\n|     |        |      |\n\nEmpty **by design**: the map draws no control and takes no focus. A step the application\nmakes pressable brings its own keyboard with the projected element\n([`req-api-platform`](../requirements/api.md#req-api-platform)).",
      "decisions": [],
      "evidence": {
        "mutation": {
          "score": 89.8,
          "killed": 44,
          "survived": 5,
          "mutants": 49,
          "files": 1
        },
        "e2e": {
          "cases": 6,
          "spec": "apps/sandbox-e2e/src/stepper.spec.ts"
        },
        "pairs": 9,
        "baselines": 2,
        "cost": {
          "elements": 22,
          "depth": 6,
          "listeners": 2,
          "renders": 1,
          "micros": 8775,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "progress",
      "classes": [
        "PctProgress"
      ],
      "role": "how far along a task is, or that it is under way at all",
      "category": "Data & status",
      "status": "released",
      "entrypoint": "@pacit/components/progress",
      "selectors": [
        "pct-progress"
      ],
      "harnesses": [
        "PctProgressHarness"
      ],
      "docs": "/components/progress",
      "summary": "How far along a task is — or, when nobody can say, that it is under way at all.",
      "usage": {
        "code": "<pct-progress [value]=\"42\" ariaLabel=\"Uploading\" />",
        "lang": "html"
      },
      "examples": [
        {
          "key": "states",
          "title": "Determinate, indeterminate, and the sizes",
          "code": "import { Component } from '@angular/core';\nimport { PctProgress } from '@pacit/components/progress';\n\n/**\n * Determinate, indeterminate, and the sizes\n *\n * A `value` makes the bar say how far; no value makes it say \"still going\", and the fill\n * travels instead. `max` sets the scale — a download in bytes needs no arithmetic first.\n */\n@Component({\n  selector: 'demo-progress-states',\n  imports: [PctProgress],\n  // A width, not only a ceiling — see `progress.demo.ts` (`lesson-160`).\n  styles: ':host { display: grid; gap: 14px; inline-size: min(28rem, 100%); }',\n  template: `\n    <pct-progress [value]=\"3\" [max]=\"8\" ariaLabel=\"Uploaded files\" />\n    <pct-progress ariaLabel=\"Connecting\" />\n    <pct-progress [value]=\"66\" size=\"sm\" ariaLabel=\"Small\" />\n    <pct-progress [value]=\"66\" size=\"lg\" ariaLabel=\"Large\" />\n  `,\n})\nexport class ProgressStatesDemo {}"
        }
      ],
      "api": [
        {
          "name": "PctProgress",
          "kind": "component",
          "selector": "pct-progress",
          "description": "A progress bar: how far along a task is, or that it is under way at all.",
          "members": [
            {
              "name": "value",
              "kind": "input",
              "required": false,
              "type": "number | null",
              "default": "null",
              "description": "How far along, in the same unit as max. null — the default — is indeterminate: the task is under way and how far is not known.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "max",
              "kind": "input",
              "required": false,
              "type": "number",
              "default": "DEFAULT_MAX",
              "description": "The value that means \"done\" — 100 by default, so a bare [value]=\"40\" is forty per cent.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabel",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The accessible name — what is progressing. There is no default: see the class note.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "ariaLabelledby",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The id of the element that already names this bar — a caption or a heading standing beside it. The usual case: the sentence a sighted user reads is the name a screen-reader user should hear, and repeating it in ariaLabel is two strings to keep in step.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "size",
              "kind": "input",
              "required": false,
              "type": "PctSize",
              "default": "config.defaultSize",
              "description": "Size; taken from the global configuration by default (req-api-config).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "tone",
              "kind": "input",
              "required": false,
              "type": "PctTone | null",
              "default": "null",
              "description": "What the work IS, drawn as a mark beside the bar and as the colour of the fill — not how far it got, which is value. A failed upload is tone: 'danger' at whatever percentage it stopped at; a finished one is 'success' at 100. An indeterminate bar may carry a tone too: \"retrying, and the last attempt failed\" is a real state, and nothing here pretends to know what the work will do next.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-progress",
              "note": ""
            },
            {
              "attribute": "data-pct-size",
              "kind": "attribute",
              "bound": "size()",
              "note": ""
            },
            {
              "attribute": "data-pct-indeterminate",
              "kind": "attribute",
              "bound": "value() === null ? \"\" : null",
              "note": "The state a skin reads, and the one the stylesheet's band rule turns on. It is written where the paint is — the ARIA half of the same fact is the <code>&lt;progress&gt;</code>'s own, and this component writes no ARIA anywhere (0039)."
            },
            {
              "attribute": "data-pct-tone",
              "kind": "attribute",
              "bound": "tone()",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctProgress",
          "kind": "component",
          "selector": "pct-progress",
          "detail": null,
          "description": "A progress bar: how far along a task is, or that it is under way at all.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "bar",
          "description": "the box that clips — the groove and the fill live inside it"
        },
        {
          "name": "fill",
          "description": "the part that is done"
        },
        {
          "name": "icon",
          "description": "the tone's mark, drawn only when the bar carries a tone"
        },
        {
          "name": "track",
          "description": "the full length"
        }
      ],
      "tokens": [
        {
          "name": "--pct-progress-fill-bg",
          "type": "color",
          "ref": "{pct.primary}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Background of the fill."
        },
        {
          "name": "--pct-progress-fill-bg-danger",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Colour of the filled part of a bar whose work failed."
        },
        {
          "name": "--pct-progress-fill-bg-info",
          "type": "color",
          "ref": "{pct.info}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Colour of the filled part of a bar reporting something informational."
        },
        {
          "name": "--pct-progress-fill-bg-success",
          "type": "color",
          "ref": "{pct.success}",
          "light": "#15803d",
          "dark": "#4ade80",
          "description": "Colour of the filled part of a bar whose work succeeded."
        },
        {
          "name": "--pct-progress-fill-bg-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Colour of the filled part of a bar whose work needs attention."
        },
        {
          "name": "--pct-progress-fill-size",
          "type": "dimension",
          "ref": null,
          "light": "33%",
          "dark": "33%",
          "description": "Length of the fill while indeterminate, as a share of the track."
        },
        {
          "name": "--pct-progress-icon-gap",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Space between the tone's mark and the bar."
        },
        {
          "name": "--pct-progress-icon-size",
          "type": "dimension",
          "ref": null,
          "light": "16px",
          "dark": "16px",
          "description": "Size of the box the tone's mark is drawn in, beside the bar."
        },
        {
          "name": "--pct-progress-track-bg",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Background of the track."
        },
        {
          "name": "--pct-progress-track-height",
          "type": "dimension",
          "ref": null,
          "light": "8px",
          "dark": "8px",
          "description": "Height of the track."
        },
        {
          "name": "--pct-progress-track-height-lg",
          "type": "dimension",
          "ref": null,
          "light": "12px",
          "dark": "12px",
          "description": "Height of the track at size lg."
        },
        {
          "name": "--pct-progress-track-height-sm",
          "type": "dimension",
          "ref": null,
          "light": "4px",
          "dark": "4px",
          "description": "Height of the track at size sm."
        },
        {
          "name": "--pct-progress-track-radius",
          "type": "dimension",
          "ref": null,
          "light": "999px",
          "dark": "999px",
          "description": "Corner radius of the track."
        }
      ],
      "texts": [],
      "keyboard": "| key | effect | test |\n| --- | ------ | ---- |\n|     |        |      |\n\nEmpty **by design**, and for a stronger reason than usual: a progress bar is not a control.\nNothing is pressed, nothing is dragged, and a `<progress>` takes focus in no engine — measured\nin `apps/sandbox-e2e/src/progress.spec.ts`, which asks the element to focus itself and asserts\nthat it does not, then walks `Tab` past it\n([`req-api-platform`](../requirements/api.md#req-api-platform)).",
      "decisions": [],
      "evidence": {
        "mutation": {
          "score": 92.31,
          "killed": 48,
          "survived": 4,
          "mutants": 52,
          "files": 1
        },
        "e2e": {
          "cases": 11,
          "spec": "apps/sandbox-e2e/src/progress.spec.ts"
        },
        "pairs": 11,
        "baselines": 2,
        "cost": {
          "elements": 13,
          "depth": 3,
          "listeners": 2,
          "renders": 1,
          "micros": 5452,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "badge",
      "classes": [
        "PctBadge"
      ],
      "role": "a word wearing a tone",
      "category": "Data & status",
      "status": "released",
      "entrypoint": "@pacit/components/badge",
      "selectors": [
        "pct-badge"
      ],
      "harnesses": [
        "PctBadgeHarness"
      ],
      "docs": "/components/badge",
      "summary": "A short word wearing a tone: a status, a count, a label beside something that already has a name.",
      "usage": {
        "code": "<pct-badge>Draft</pct-badge>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "tones",
          "title": "Tones",
          "code": "import { Component } from '@angular/core';\nimport { PctBadge } from '@pacit/components/badge';\n\n/**\n * Tones\n *\n * Four tones and the absence of one. A badge with no `tone` wears the skin's quiet\n * surfaces; the four named ones are `PctTone`, the same list the toast and the button\n * read, and every word over every box is a measured pair on both themes.\n */\n@Component({\n  selector: 'demo-badge-tones',\n  imports: [PctBadge],\n  styles:\n    ':host { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }',\n  template: `\n    <pct-badge>Draft</pct-badge>\n    <pct-badge tone=\"danger\">3 overdue</pct-badge>\n    <pct-badge tone=\"warning\">Expiring</pct-badge>\n    <pct-badge tone=\"success\">Paid</pct-badge>\n    <pct-badge tone=\"info\">Scheduled</pct-badge>\n  `,\n})\nexport class BadgeTonesDemo {}"
        },
        {
          "key": "statuses",
          "title": "A column of statuses",
          "code": "import { Component } from '@angular/core';\nimport { PctBadge } from '@pacit/components/badge';\nimport type { PctTone } from '@pacit/components/core';\n\n/**\n * A column of statuses\n *\n * Where a tone earns its keep: one column, one word per row, and the colour repeating what\n * the word already says. The word is what a screen reader reads — the tone is for the eye\n * that scans the column and stops at the red one. The last row binds `null`, which is how\n * a badge asks for no tone at all.\n */\n@Component({\n  selector: 'demo-badge-statuses',\n  imports: [PctBadge],\n  styles: `\n    :host {\n      display: block;\n    }\n    table {\n      border-collapse: collapse;\n      width: 100%;\n      max-width: 32rem;\n    }\n    th,\n    td {\n      text-align: start;\n      padding: 8px 12px;\n      border-bottom: 1px solid var(--pct-border);\n    }\n    th {\n      font-weight: 600;\n    }\n  `,\n  template: `\n    <table>\n      <thead>\n        <tr>\n          <th scope=\"col\">Invoice</th>\n          <th scope=\"col\">State</th>\n        </tr>\n      </thead>\n      <tbody>\n        @for (row of rows; track row.id) {\n          <tr>\n            <td>{{ row.id }}</td>\n            <td>\n              <pct-badge [tone]=\"row.tone\">{{ row.state }}</pct-badge>\n            </td>\n          </tr>\n        }\n      </tbody>\n    </table>\n  `,\n})\nexport class BadgeStatusesDemo {\n  readonly rows: { id: string; state: string; tone: PctTone | null }[] = [\n    { id: '#1043', state: 'Paid', tone: 'success' },\n    { id: '#1044', state: 'Due in 3 days', tone: 'warning' },\n    { id: '#1045', state: 'Overdue', tone: 'danger' },\n    { id: '#1046', state: 'Scheduled', tone: 'info' },\n    { id: '#1047', state: 'Draft', tone: null },\n  ];\n}"
        }
      ],
      "api": [
        {
          "name": "PctBadge",
          "kind": "component",
          "selector": "pct-badge",
          "description": "A badge: a word wearing a tone — Draft, Active, Overdue.",
          "members": [
            {
              "name": "tone",
              "kind": "input",
              "required": false,
              "type": "PctTone | null",
              "default": "null",
              "description": "The tone the word wears, or null for none — the default, and the quiet box the skin's own surfaces paint. It repeats the text, never replaces it.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-badge",
              "note": ""
            },
            {
              "attribute": "data-pct-tone",
              "kind": "attribute",
              "bound": "tone()",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctBadge",
          "kind": "component",
          "selector": "pct-badge",
          "detail": null,
          "description": "A badge: a word wearing a tone — Draft, Active, Overdue.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [],
      "tokens": [
        {
          "name": "--pct-badge-bg",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the badge."
        },
        {
          "name": "--pct-badge-bg-danger",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Background of the badge in the danger tone."
        },
        {
          "name": "--pct-badge-bg-info",
          "type": "color",
          "ref": "{pct.info}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Background of the badge in the info tone."
        },
        {
          "name": "--pct-badge-bg-success",
          "type": "color",
          "ref": "{pct.success}",
          "light": "#15803d",
          "dark": "#4ade80",
          "description": "Background of the badge in the success tone."
        },
        {
          "name": "--pct-badge-bg-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Background of the badge in the warning tone."
        },
        {
          "name": "--pct-badge-border",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge colour of the badge."
        },
        {
          "name": "--pct-badge-border-danger",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Edge colour of the badge in the danger tone."
        },
        {
          "name": "--pct-badge-border-info",
          "type": "color",
          "ref": "{pct.info}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Edge colour of the badge in the info tone."
        },
        {
          "name": "--pct-badge-border-success",
          "type": "color",
          "ref": "{pct.success}",
          "light": "#15803d",
          "dark": "#4ade80",
          "description": "Edge colour of the badge in the success tone."
        },
        {
          "name": "--pct-badge-border-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Edge colour of the badge in the warning tone."
        },
        {
          "name": "--pct-badge-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the badge."
        },
        {
          "name": "--pct-badge-fg-danger",
          "type": "color",
          "ref": "{pct.on-danger}",
          "light": "#ffffff",
          "dark": "#000000",
          "description": "Text colour of the badge in the danger tone."
        },
        {
          "name": "--pct-badge-fg-info",
          "type": "color",
          "ref": "{pct.on-info}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Text colour of the badge in the info tone."
        },
        {
          "name": "--pct-badge-fg-success",
          "type": "color",
          "ref": "{pct.on-success}",
          "light": "#ffffff",
          "dark": "#000000",
          "description": "Text colour of the badge in the success tone."
        },
        {
          "name": "--pct-badge-fg-warning",
          "type": "color",
          "ref": "{pct.on-warning}",
          "light": "#ffffff",
          "dark": "#000000",
          "description": "Text colour of the badge in the warning tone."
        },
        {
          "name": "--pct-badge-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of the badge."
        },
        {
          "name": "--pct-badge-font-weight",
          "type": "fontWeight",
          "ref": "{pct.font.weight.medium}",
          "light": "500",
          "dark": "500",
          "description": "Font weight of the badge."
        },
        {
          "name": "--pct-badge-gap",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Gap between the badge's text and its icon."
        },
        {
          "name": "--pct-badge-padding-x",
          "type": "dimension",
          "ref": "{pct.space.2}",
          "light": "4px",
          "dark": "4px",
          "description": "Horizontal padding of the badge."
        },
        {
          "name": "--pct-badge-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the badge."
        }
      ],
      "texts": [],
      "keyboard": "| key | effect | test |\n| --- | ------ | ---- |\n|     |        |      |\n\nEmpty **by design**: a badge is running text, holds no control, and takes no focus — the\nplatform provides nothing here because there is nothing to provide\n([`req-api-platform`](../requirements/api.md#req-api-platform)).",
      "decisions": [],
      "evidence": {
        "mutation": {
          "score": 81.82,
          "killed": 9,
          "survived": 2,
          "mutants": 11,
          "files": 1
        },
        "e2e": {
          "cases": 6,
          "spec": "apps/sandbox-e2e/src/badge.spec.ts"
        },
        "pairs": 14,
        "baselines": 2,
        "cost": {
          "elements": 3,
          "depth": 1,
          "listeners": 0,
          "renders": 1,
          "micros": 2546,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "icon",
      "classes": [
        "PctIcon"
      ],
      "role": "a drawing in a box the text sizes and colours",
      "category": "Data & status",
      "status": "released",
      "entrypoint": "@pacit/components/icon",
      "selectors": [
        "pct-icon"
      ],
      "harnesses": [
        "PctIconHarness"
      ],
      "docs": "/components/icon",
      "summary": "An icon from any set — a font, SVG drawings as data, a sprite, your own component — in a box the text sizes and colours; one line registers the set and dresses the library in it.",
      "usage": {
        "code": "<pct-icon icon=\"trash\" tone=\"danger\" />",
        "lang": "html"
      },
      "examples": [
        {
          "key": "prime",
          "title": "PrimeIcons",
          "code": "import { Component } from '@angular/core';\nimport { PctIcon, primeIcons, providePctIcons } from '@pacit/components/icon';\n\n/**\n * PrimeIcons\n *\n * `primeIcons()` is the adapter: `<pct-icon icon=\"bell\">` holds a span wearing `pi pi-bell`,\n * and PrimeIcons' own stylesheet — yours to include, in the `styles` of `angular.json` —\n * draws the glyph. The library brings no font and no list of names: whatever the stylesheet\n * knows is an id here. The same line carries the library's eleven roles in PrimeIcons'\n * spelling, so the select's arrow and the toast's marks wear the set too — the example\n * \"One line dresses the library\" shows that side, and \"Which roles a set dresses\" how to\n * keep some of them.\n */\n@Component({\n  selector: 'demo-icon-prime',\n  imports: [PctIcon],\n  providers: [providePctIcons(primeIcons())],\n  styles:\n    ':host { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; font-size: 24px; }',\n  template: `\n    <pct-icon icon=\"home\" />\n    <pct-icon icon=\"search\" />\n    <pct-icon icon=\"shopping-cart\" />\n    <pct-icon icon=\"heart-fill\" tone=\"danger\" />\n    <pct-icon icon=\"github\" />\n    <pct-icon icon=\"cog\" />\n  `,\n})\nexport class IconPrimeDemo {}"
        },
        {
          "key": "material",
          "title": "Material Icons",
          "code": "import { Component } from '@angular/core';\nimport {\n  materialIcons,\n  PctIcon,\n  providePctIcons,\n} from '@pacit/components/icon';\n\n/**\n * Material Icons\n *\n * A ligature font: the text of the span is the id — `home` — and the font draws the word as\n * one glyph. `materialIcons()` writes both, the class and the text, and the box hides the\n * word from a screen reader so that a named icon is not read twice. `variant` names the font\n * loaded — `outlined` here, the `material-icons-outlined` class of the stylesheet. Material\n * Symbols, the variable font, is `materialSymbols()` with its axes.\n */\n@Component({\n  selector: 'demo-icon-material',\n  imports: [PctIcon],\n  providers: [providePctIcons(materialIcons({ variant: 'outlined' }))],\n  styles:\n    ':host { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; font-size: 24px; }',\n  template: `\n    <pct-icon icon=\"home\" />\n    <pct-icon icon=\"search\" />\n    <pct-icon icon=\"shopping_cart\" />\n    <pct-icon icon=\"favorite\" tone=\"danger\" />\n    <pct-icon icon=\"notifications\" />\n    <pct-icon icon=\"settings\" />\n  `,\n})\nexport class IconMaterialDemo {}"
        },
        {
          "key": "fontawesome",
          "title": "Font Awesome",
          "code": "import { Component } from '@angular/core';\nimport { fontAwesome, PctIcon, providePctIcons } from '@pacit/components/icon';\n\n// The regular style in a scope of its own: the same six ids, drawn in outline. A source is\n// an ordinary provider, so a row can hold a style the row beside it does not.\n@Component({\n  selector: 'demo-icon-fontawesome-regular',\n  imports: [PctIcon],\n  providers: [providePctIcons(fontAwesome({ style: 'regular' }))],\n  styles: ':host { display: contents; }',\n  template: `\n    <pct-icon icon=\"heart\" tone=\"danger\" />\n    <pct-icon icon=\"bell\" />\n    <pct-icon icon=\"star\" tone=\"warning\" />\n    <pct-icon icon=\"user\" />\n    <pct-icon icon=\"circle-check\" tone=\"success\" />\n    <pct-icon icon=\"calendar\" />\n  `,\n})\nexport class IconFontAwesomeRegular {}\n\n/**\n * Font Awesome\n *\n * `fontAwesome()` is the free solid style: `<pct-icon icon=\"heart\">` holds a span wearing\n * `fa-solid fa-heart`, drawn by the stylesheet you include. `style` picks another of the\n * font's — `regular` in the second row, `brands` for the logos — and a version-5 sheet takes\n * its three-letter prefix in `prefix`. The roles map spells the names of versions 6 and 7; a sheet\n * that spells one differently corrects it in `roles`, which is what `{ close: 'times' }` is\n * for on version 5.\n */\n@Component({\n  selector: 'demo-icon-fontawesome',\n  imports: [PctIcon, IconFontAwesomeRegular],\n  providers: [providePctIcons(fontAwesome())],\n  styles: `\n    :host {\n      display: flex;\n      flex-direction: column;\n      gap: 16px;\n      font-size: 24px;\n    }\n    .row {\n      display: flex;\n      flex-wrap: wrap;\n      align-items: center;\n      gap: 16px;\n    }\n  `,\n  template: `\n    <div class=\"row\">\n      <pct-icon icon=\"heart\" tone=\"danger\" />\n      <pct-icon icon=\"bell\" />\n      <pct-icon icon=\"star\" tone=\"warning\" />\n      <pct-icon icon=\"user\" />\n      <pct-icon icon=\"circle-check\" tone=\"success\" />\n      <pct-icon icon=\"calendar\" />\n    </div>\n    <div class=\"row\">\n      <demo-icon-fontawesome-regular />\n    </div>\n  `,\n})\nexport class IconFontAwesomeDemo {}"
        },
        {
          "key": "svg",
          "title": "Your own SVG",
          "code": "import { Component } from '@angular/core';\nimport { PctIcon, providePctIcons } from '@pacit/components/icon';\nimport { svgIcons } from '@pacit/components/svg-icon';\n\n/**\n * Your own SVG\n *\n * A drawing is data — a `viewBox`, the root's paint and the elements inside it — rendered one\n * element at a time, so no string of markup is ever trusted. `svgIcons()` registers drawings\n * under the ids you choose. Written out, a drawing names its own grid and paint, like the\n * mark here; a bare node list is read on the 24-grid with a round 2-unit stroke, the shape\n * lucide and Tabler export, so their icons drop in as they are; a definition from\n * `@fortawesome/free-solid-svg-icons` does too. A one-off goes into the box as content with\n * no source at all, and a folder of files a designer exported becomes\n * `svgSprite('icons.svg')`.\n */\n@Component({\n  selector: 'demo-icon-svg',\n  imports: [PctIcon],\n  providers: [\n    providePctIcons(\n      svgIcons({\n        mark: {\n          viewBox: '0 0 32 32',\n          attributes: { fill: 'currentColor', 'fill-rule': 'evenodd' },\n          nodes: [\n            [\n              'path',\n              { d: 'M16 2l13 7.5v13L16 30 3 22.5v-13zm0 7l-7 4v6l7 4 7-4v-6z' },\n            ],\n          ],\n        },\n        pin: [\n          ['path', { d: 'M12 21s-7-5.5-7-11a7 7 0 0 1 14 0c0 5.5-7 11-7 11z' }],\n          ['circle', { cx: 12, cy: 10, r: 2.5 }],\n        ],\n      }),\n    ),\n  ],\n  styles:\n    ':host { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; font-size: 24px; }',\n  template: `\n    <pct-icon icon=\"mark\" />\n    <pct-icon icon=\"pin\" tone=\"info\" />\n    <pct-icon label=\"Draft\">\n      <svg\n        viewBox=\"0 0 24 24\"\n        width=\"100%\"\n        height=\"100%\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n      >\n        <path d=\"M4 20h16M6 16 16 6l2 2L8 18z\" />\n      </svg>\n    </pct-icon>\n  `,\n})\nexport class IconSvgDemo {}"
        },
        {
          "key": "webfont",
          "title": "Your own icon font",
          "code": "import { Component, ViewEncapsulation } from '@angular/core';\nimport { iconFont, PctIcon, providePctIcons } from '@pacit/components/icon';\n\n/**\n * Your own icon font\n *\n * An icon font is a class per glyph and a stylesheet that draws it, and that is all\n * `iconFont()` asks for: the classes an id becomes. A font that reads a ligature adds\n * `text`, a variable font its axes in `style`, and `roles` names the library's roles in the\n * font's spelling. The four rules under this stage stand in for a `@font-face` and its\n * glyph classes — the same mechanism PrimeIcons and Font Awesome run on.\n */\n@Component({\n  selector: 'demo-icon-webfont',\n  imports: [PctIcon],\n  providers: [providePctIcons(iconFont({ class: (id) => `acme acme-${id}` }))],\n  // A font's stylesheet is global, as a real one is: the rules have to reach the glyph span\n  // inside the box, where a component's scoped styles do not.\n  encapsulation: ViewEncapsulation.None,\n  styles: `\n    demo-icon-webfont {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 16px;\n      align-items: center;\n      font-size: 24px;\n    }\n    .acme {\n      font-family: system-ui, sans-serif;\n      font-weight: 700;\n      line-height: 1;\n    }\n    .acme-star::before {\n      content: '★';\n    }\n    .acme-sun::before {\n      content: '☀';\n    }\n    .acme-moon::before {\n      content: '☾';\n    }\n    .acme-flag::before {\n      content: '⚑';\n    }\n  `,\n  template: `\n    <pct-icon icon=\"star\" tone=\"warning\" />\n    <pct-icon icon=\"sun\" />\n    <pct-icon icon=\"moon\" />\n    <pct-icon icon=\"flag\" tone=\"danger\" />\n  `,\n})\nexport class IconWebfontDemo {}"
        },
        {
          "key": "mixed",
          "title": "Several sets at once",
          "code": "import { Component } from '@angular/core';\nimport {\n  fontAwesome,\n  PctIcon,\n  PctIconSource,\n  primeIcons,\n  providePctIcons,\n} from '@pacit/components/icon';\nimport { svgIcons } from '@pacit/components/svg-icon';\nimport { Bell, House } from 'lucide';\n\n// A source is a plain object, so one that answers only under a prefix — and hands the rest\n// of the id on — is three lines. It carries no roles: the font behind it dresses nothing.\nconst under = (prefix: string, source: PctIconSource): PctIconSource => ({\n  resolve: (id) =>\n    id.startsWith(prefix) ? source.resolve(id.slice(prefix.length)) : null,\n});\n\n/**\n * Several sets at once\n *\n * The arguments of `providePctIcons()` are asked in order, and the first that answers draws.\n * Drawings answer for their ids and `null` for the rest, so they stand first; a font answers\n * for every id, so it stands last — and a second font behind it would never be reached. A\n * prefix makes room for one: `brand:github` goes to Font Awesome's brands, and everything\n * the drawings do not know goes to PrimeIcons.\n */\n@Component({\n  selector: 'demo-icon-mixed',\n  imports: [PctIcon],\n  providers: [\n    providePctIcons(\n      svgIcons({ house: House, bell: Bell }),\n      under('brand:', fontAwesome({ style: 'brands' })),\n      primeIcons(),\n    ),\n  ],\n  styles:\n    ':host { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; font-size: 24px; }',\n  template: `\n    <pct-icon icon=\"house\" />\n    <pct-icon icon=\"bell\" tone=\"warning\" />\n    <pct-icon icon=\"brand:github\" />\n    <pct-icon icon=\"brand:angular\" />\n    <pct-icon icon=\"brand:npm\" />\n    <pct-icon icon=\"heart-fill\" tone=\"danger\" />\n    <pct-icon icon=\"cog\" />\n  `,\n})\nexport class IconMixedDemo {}"
        },
        {
          "key": "dressed",
          "title": "One line dresses the library",
          "code": "import { Component } from '@angular/core';\nimport { PctAvatar } from '@pacit/components/avatar';\nimport { PctCheckbox } from '@pacit/components/checkbox';\nimport { primeIcons, providePctIcons } from '@pacit/components/icon';\n\n/**\n * One line dresses the library\n *\n * A source that knows the library's roles in its own vocabulary swaps the drawings every\n * component ships with: `providePctIcons(primeIcons())` on this card, and the avatar's\n * silhouette and the checkbox's tick are PrimeIcons' — `pi pi-user`, `pi pi-check`. It is an\n * ordinary provider, so the same line at bootstrap dresses the whole application, and here\n * it stops at the edge of the card: the pair on the right draws what the components ship.\n */\n@Component({\n  selector: 'demo-icon-dressed-card',\n  imports: [PctAvatar, PctCheckbox],\n  providers: [providePctIcons(primeIcons())],\n  styles: ':host { display: inline-flex; align-items: center; gap: 12px; }',\n  template: `\n    <pct-avatar />\n    <pct-checkbox label=\"PrimeIcons\" [checked]=\"true\" />\n  `,\n})\nexport class IconDressedCard {}\n\n@Component({\n  selector: 'demo-icon-dressed',\n  imports: [IconDressedCard, PctAvatar, PctCheckbox],\n  styles: `\n    :host {\n      display: flex;\n      flex-wrap: wrap;\n      align-items: center;\n      gap: 32px;\n    }\n    .plain {\n      display: inline-flex;\n      align-items: center;\n      gap: 12px;\n    }\n  `,\n  template: `\n    <demo-icon-dressed-card />\n    <span class=\"plain\">\n      <pct-avatar />\n      <pct-checkbox label=\"Built in\" [checked]=\"true\" />\n    </span>\n  `,\n})\nexport class IconDressedDemo {}"
        },
        {
          "key": "roles",
          "title": "Which roles a set dresses",
          "code": "import { Component } from '@angular/core';\nimport { PctAvatar } from '@pacit/components/avatar';\nimport { PctCheckbox } from '@pacit/components/checkbox';\nimport {\n  materialIcons,\n  PctIcon,\n  providePctIcons,\n} from '@pacit/components/icon';\nimport { PctSelect, PctSelectOption } from '@pacit/components/select';\n\n// One scene three times over: the select draws the `chevron-down` role, the checkbox the\n// `check` role and the avatar the `user` role; the bell is an id of yours. Only the\n// providers differ between the cards.\nconst SCENE = `\n  <pct-select ariaLabel=\"Fruit\" [options]=\"fruit\" value=\"apple\" />\n  <span class=\"row\">\n    <pct-checkbox label=\"Ripe\" [checked]=\"true\" />\n    <pct-avatar />\n    <pct-icon icon=\"notifications\" size=\"lg\" />\n  </span>\n`;\nconst FRUIT: readonly PctSelectOption[] = [\n  { value: 'apple', label: 'Apple' },\n  { value: 'pear', label: 'Pear' },\n];\nconst CARD = `\n  :host { display: grid; gap: 12px; }\n  .row { display: flex; align-items: center; gap: 12px; }\n`;\n\n// No roles at all: Material Icons for your ids, and every component keeps the drawing it\n// ships with.\n@Component({\n  selector: 'demo-icon-roles-none',\n  imports: [PctIcon, PctSelect, PctCheckbox, PctAvatar],\n  providers: [\n    providePctIcons(materialIcons({ variant: 'outlined', roles: [] })),\n  ],\n  styles: CARD,\n  template: SCENE,\n})\nexport class IconRolesNone {\n  readonly fruit = FRUIT;\n}\n\n// One role, in the adapter's spelling: the select's arrow is Material's `expand_more`, and\n// the other nine drawings stay the library's.\n@Component({\n  selector: 'demo-icon-roles-arrow',\n  imports: [PctIcon, PctSelect, PctCheckbox, PctAvatar],\n  providers: [\n    providePctIcons(\n      materialIcons({ variant: 'outlined', roles: ['chevron-down'] }),\n    ),\n  ],\n  styles: CARD,\n  template: SCENE,\n})\nexport class IconRolesArrow {\n  readonly fruit = FRUIT;\n}\n\n// The adapter's whole map with one role handed back: Material's arrow and silhouette, the\n// library's tick.\n@Component({\n  selector: 'demo-icon-roles-tick',\n  imports: [PctIcon, PctSelect, PctCheckbox, PctAvatar],\n  providers: [\n    providePctIcons(\n      materialIcons({ variant: 'outlined', roles: { check: undefined } }),\n    ),\n  ],\n  styles: CARD,\n  template: SCENE,\n})\nexport class IconRolesTick {\n  readonly fruit = FRUIT;\n}\n\n/**\n * Which roles a set dresses\n *\n * The `roles` of a source are what dresses the library, and they are yours to choose. A\n * list names the roles to dress and nothing else, each in the adapter's spelling: `[]` is a\n * font that draws your ids and leaves every component as it ships, `['chevron-down']` is\n * the select's arrow and nothing else. A map corrects the adapter's own, and a role set to\n * `undefined` in it is handed back to the library — Material everywhere but the checkbox's\n * tick. `primeIcons({ roles: [] })`, `fontAwesome()` and the others take `roles` the same\n * way; `svgIcons()` takes the map.\n */\n@Component({\n  selector: 'demo-icon-roles',\n  imports: [IconRolesNone, IconRolesArrow, IconRolesTick],\n  styles: `\n    :host {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 24px 32px;\n      align-items: start;\n    }\n    figure {\n      display: grid;\n      gap: 12px;\n      margin: 0;\n      inline-size: 10rem;\n    }\n    figcaption {\n      font-size: 0.875rem;\n      color: var(--pct-text-muted);\n    }\n  `,\n  template: `\n    <figure>\n      <demo-icon-roles-none />\n      <figcaption>No roles: nothing of the library's replaced</figcaption>\n    </figure>\n    <figure>\n      <demo-icon-roles-arrow />\n      <figcaption>One role: the arrow, and nothing else</figcaption>\n    </figure>\n    <figure>\n      <demo-icon-roles-tick />\n      <figcaption>The adapter's map, less the tick</figcaption>\n    </figure>\n  `,\n})\nexport class IconRolesDemo {}"
        },
        {
          "key": "tones",
          "title": "Tones and sizes",
          "code": "import { Component } from '@angular/core';\nimport { PctIcon, providePctIcons } from '@pacit/components/icon';\nimport { svgIcons } from '@pacit/components/svg-icon';\nimport { Bell, CircleAlert, CircleCheck, Info, TriangleAlert } from 'lucide';\n\n/**\n * Tones and sizes\n *\n * The four tones are `PctTone`, the same list the toast, the badge and the button read; an\n * icon wearing one takes the skin's colour and nothing else changes. The three sizes are\n * steps on the icon's own tokens, and no `size` is `1em` — the text's own, the right size\n * beside a word. The drawings are lucide's, rendered from the data the package exports.\n */\n@Component({\n  selector: 'demo-icon-tones',\n  imports: [PctIcon],\n  providers: [\n    providePctIcons(\n      svgIcons({\n        success: CircleCheck,\n        warning: TriangleAlert,\n        danger: CircleAlert,\n        info: Info,\n        bell: Bell,\n      }),\n    ),\n  ],\n  styles: `\n    :host {\n      display: flex;\n      flex-direction: column;\n      gap: 16px;\n    }\n    .row {\n      display: flex;\n      align-items: center;\n      gap: 16px;\n    }\n  `,\n  template: `\n    <div class=\"row\">\n      <pct-icon icon=\"success\" tone=\"success\" size=\"lg\" />\n      <pct-icon icon=\"warning\" tone=\"warning\" size=\"lg\" />\n      <pct-icon icon=\"danger\" tone=\"danger\" size=\"lg\" />\n      <pct-icon icon=\"info\" tone=\"info\" size=\"lg\" />\n    </div>\n    <div class=\"row\">\n      <pct-icon icon=\"bell\" size=\"sm\" />\n      <pct-icon icon=\"bell\" size=\"md\" />\n      <pct-icon icon=\"bell\" size=\"lg\" />\n      <span>Inbox <pct-icon icon=\"bell\" /></span>\n    </div>\n  `,\n})\nexport class IconTonesDemo {}"
        }
      ],
      "api": [
        {
          "name": "PctIcon",
          "kind": "component",
          "selector": "pct-icon",
          "description": "An icon: a drawing in a box the text around it sizes and colours (req-api-icons).",
          "members": [
            {
              "name": "name",
              "kind": "input",
              "required": false,
              "type": "PctIconName | null",
              "default": "null",
              "description": "Which ROLE this icon plays in a component of the library. A component always names one; a consumer dropping a one-off drawing into their own markup leaves it out, and then the content is all there is. Resolved through the roles of each source, in order.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "icon",
              "kind": "input",
              "required": false,
              "type": "PctIconId | null",
              "default": "null",
              "description": "Which icon of the CONSUMER's set this is — house, trash, whatever the sources answer for. string until the application augments PctIconIds, and then exactly its keys. Not for a component of the library: those name a role.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "tone",
              "kind": "input",
              "required": false,
              "type": "PctTone | null",
              "default": "null",
              "description": "The colour of the skin's four tones, or null for the colour of the text around it. An icon IS the second channel a tone needs — a shape that stays when the colour goes — so a tone here never speaks alone; what it cannot do is tell two states apart by colour on one and the same glyph (0076).",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "size",
              "kind": "input",
              "required": false,
              "type": "PctSize | null",
              "default": "null",
              "description": "A step on the icon's own scale, or null for the text's own size (1em) — the default, and the right one beside text. The steps are tokens, so a theme moves them (req-token-override); any other size is font-size on the box.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            },
            {
              "name": "label",
              "kind": "input",
              "required": false,
              "type": "string | null",
              "default": "null",
              "description": "The accessible name, for an icon that carries information nothing else in the page says. With one the element is role=\"img\" and read by that name; without one it is hidden from the accessibility tree, which is right for every icon that stands beside the words it repeats.",
              "since": "next",
              "unreleased": true,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-icon",
              "note": ""
            },
            {
              "attribute": "aria-hidden",
              "kind": "attribute",
              "bound": "named() === null ? \"true\" : null",
              "note": "Decoration unless it is given a name: beside the text it belongs to — the select's arrow next to the chosen value, the checkbox's tick inside a labelled control — a second reading of the same thing is noise in a screen reader. Named, it is an image."
            },
            {
              "attribute": "role",
              "kind": "attribute",
              "bound": "named() === null ? null : \"img\"",
              "note": ""
            },
            {
              "attribute": "aria-label",
              "kind": "attribute",
              "bound": "named()",
              "note": ""
            },
            {
              "attribute": "data-pct-tone",
              "kind": "attribute",
              "bound": "tone()",
              "note": ""
            },
            {
              "attribute": "data-pct-size",
              "kind": "attribute",
              "bound": "size()",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctIconName",
          "kind": "type",
          "selector": null,
          "detail": "| 'calendar' | 'check' | 'chevron-down' | 'clock' | 'close' | 'danger' | 'indeterminate' | 'info' | 'success' | 'user' | 'warning' | 'zoom-in' | 'zoom-out' | 'previous' | 'next' | 'reset' | 'filter'",
          "description": "The icons the library draws, by the ROLE they play rather than by what they look like (req-api-icons). chevron-down and not arrow-down-16: a consumer swapping it is answering \"what does the select's trigger show\", not \"which of my 16-pixel arrows\".",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctIconSetOrSource",
          "kind": "type",
          "selector": null,
          "detail": "Type<unknown> | PctIconSource",
          "description": "What providePctIcons takes, any number of times: a set component, or a source.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PCT_ICONS",
          "kind": "token",
          "selector": null,
          "detail": null,
          "description": "ONE set component, as 0028 wrote it — kept in that shape so that whoever read or provided it keeps compiling and running. A set standing here is read first, before everything PCT_ICON_SOURCES carries.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PCT_ICON_SOURCES",
          "kind": "token",
          "selector": null,
          "detail": null,
          "description": "The consumer's icons, in order of precedence: set components and sources alike — what providePctIcons(…) provides (0083). A source holds no markup at all, which is why it can be a plain object.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "providePctIcons",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Registers the application's icons, for the whole application or for one subtree (req-api-icons). Every argument is a set component or a source, asked in this order: the first that answers draws. A role no argument answers keeps the drawing the component ships with, the same partial override as providePctTexts.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctIconTemplate",
          "kind": "directive",
          "selector": "ng-template[pctIcon]",
          "detail": null,
          "description": "One icon of a set: an <ng-template> under the name it replaces (req-api-icons).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctIcon",
          "kind": "component",
          "selector": "pct-icon",
          "detail": null,
          "description": "An icon: a drawing in a box the text around it sizes and colours (req-api-icons).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctIconIds",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "",
          "since": null,
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctIconId",
          "kind": "type",
          "selector": null,
          "detail": "[keyof PctIconIds] extends [never] ? string : keyof PctIconIds & string",
          "description": "What icon takes: every key of PctIconIds, or any string while that interface is empty.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctIconTemplateRendering",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "A drawing a set component registered under a role: the template renders in the box.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctIconFontRendering",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "A glyph of an icon font: a <span> wearing the font's classes, holding the ligature text when the font reads one (Material), with font-variation-settings or whatever else the font takes as inline style.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctIconComponentRendering",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "A component created in the box with the inputs given — the way every drawing that is not a template and not a font arrives, including the library's own SVG renderer.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctIconRendering",
          "kind": "type",
          "selector": null,
          "detail": "PctIconTemplateRendering | PctIconFontRendering | PctIconComponentRendering",
          "description": "What a source answers for an id: a template, a font glyph or a component — data, never markup. A string of SVG has no place here on purpose: Angular's sanitizer deletes an <svg> from [innerHTML] outright, and the only way around it is the library trusting markup a consumer wrote, which is the shape 0028 refused.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctIconSource",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "A source of icons: answers an id with a rendering, or with null so that the next source is asked. providePctIcons(…) takes any number of them, in order of precedence.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctSvgNode",
          "kind": "type",
          "selector": null,
          "detail": "readonly [ tag: string, attributes: Readonly<Record<string, string | number | undefined>>, ]",
          "description": "One element of an SVG drawing given as data: the tag and its attributes.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctSvgIconData",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "An SVG drawing as data: the box it is drawn in, the attributes of the root and the elements inside it.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctSvgIconDefinition",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "An icon as FontAwesome's SVG packages export it: faHouse from @fortawesome/free-solid-svg-icons. Only the icon tuple is read — the width, the height and the path data, one path or two.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctSvgIconInput",
          "kind": "type",
          "selector": null,
          "detail": "PctSvgIconData | readonly PctSvgNode[] | PctSvgIconDefinition",
          "description": "What svgIcons() takes under an id: a drawing as data, the nodes alone (Tabler and the packages built like it — drawn on the 24-grid with their stroke conventions), or a FontAwesome definition.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctIconRoles",
          "kind": "type",
          "selector": null,
          "detail": "Readonly< Partial<Record<PctIconName, string | undefined>> >",
          "description": "The library's roles in a font's vocabulary — what an adapter carries so that one line dresses every component, and what a consumer overrides when their version spells one differently (times became xmark between FontAwesome 5 and 6).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctIconRoleSelection",
          "kind": "type",
          "selector": null,
          "detail": "PctIconRoles | readonly PctIconName[]",
          "description": "Which of the library's roles a font dresses, said one of two ways. A map corrects an adapter's own — { close: 'times' } for a version that spells one differently, undefined to keep the library's drawing for that one role — and the rest of the map stands; a font of your own has no map of its own, and takes the map as it is. A list names the roles to dress and nothing else, each in the adapter's spelling: ['chevron-down'] dresses the select's arrow and leaves every other component as it ships, [] dresses none — the font draws your ids, and the library draws its own.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctIconFontRoles",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What every font adapter takes besides its own knobs.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctIconFontOptions",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "How an icon font keys its glyphs, for a font this entrypoint has no adapter of its own for — or for the application's own.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "iconFont",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "An icon font as a source: every id is a glyph of it (req-api-icons).",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctFontAwesomeOptions",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What FontAwesome's stylesheet is told, besides the roles.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "fontAwesome",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "FontAwesome, as classes: <pct-icon icon=\"house\"> is <i class=\"fa-solid fa-house\">.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "primeIcons",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "PrimeIcons, as classes: <pct-icon icon=\"bell\"> is <i class=\"pi pi-bell\">.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "bootstrapIcons",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Bootstrap Icons, as classes: <pct-icon icon=\"bell\"> is <i class=\"bi bi-bell\">.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctMaterialIconsVariant",
          "kind": "type",
          "selector": null,
          "detail": "'filled' | 'outlined' | 'round' | 'sharp' | 'two-tone'",
          "description": "Which of the Material Icons fonts is loaded.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctMaterialIconsOptions",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What the Material Icons font is told, besides the roles.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "materialIcons",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Material Icons, the ligature font: <pct-icon icon=\"home\"> is <span class=\"material-icons\">home</span>.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctMaterialSymbolsVariant",
          "kind": "type",
          "selector": null,
          "detail": "'outlined' | 'rounded' | 'sharp'",
          "description": "Which of the Material Symbols fonts is loaded.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "PctMaterialSymbolsOptions",
          "kind": "interface",
          "selector": null,
          "detail": null,
          "description": "What the Material Symbols font is told, besides the roles: the axes of a variable font. An axis left out is left to the stylesheet.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        },
        {
          "name": "materialSymbols",
          "kind": "function",
          "selector": null,
          "detail": null,
          "description": "Material Symbols, the variable ligature font: <pct-icon icon=\"home\"> is <span class=\"material-symbols-outlined\">home</span>, with font-variation-settings for the axes given.",
          "since": "next",
          "deprecated": null,
          "unreleased": true
        }
      ],
      "parts": [],
      "tokens": [
        {
          "name": "--pct-icon-fg-danger",
          "type": "color",
          "ref": "{pct.danger}",
          "light": "#b91c1c",
          "dark": "#f87171",
          "description": "Colour of an icon wearing the danger tone."
        },
        {
          "name": "--pct-icon-fg-info",
          "type": "color",
          "ref": "{pct.info}",
          "light": "#2563eb",
          "dark": "#60a5fa",
          "description": "Colour of an icon wearing the info tone."
        },
        {
          "name": "--pct-icon-fg-success",
          "type": "color",
          "ref": "{pct.success}",
          "light": "#15803d",
          "dark": "#4ade80",
          "description": "Colour of an icon wearing the success tone."
        },
        {
          "name": "--pct-icon-fg-warning",
          "type": "color",
          "ref": "{pct.warning}",
          "light": "#b45309",
          "dark": "#fbbf24",
          "description": "Colour of an icon wearing the warning tone."
        },
        {
          "name": "--pct-icon-size",
          "type": "dimension",
          "ref": null,
          "light": "20px",
          "dark": "20px",
          "description": "Size of the icon's box at the base step."
        },
        {
          "name": "--pct-icon-size-lg",
          "type": "dimension",
          "ref": null,
          "light": "24px",
          "dark": "24px",
          "description": "Size of the icon's box at the large step."
        },
        {
          "name": "--pct-icon-size-sm",
          "type": "dimension",
          "ref": null,
          "light": "16px",
          "dark": "16px",
          "description": "Size of the icon's box at the small step."
        }
      ],
      "texts": [],
      "keyboard": "| key | effect | test |\n| --- | ------ | ---- |\n|     |        |      |\n\nEmpty **by design**: an icon takes no focus and handles no key — the platform provides nothing\nhere because there is nothing to provide\n([`req-api-platform`](../requirements/api.md#req-api-platform)).",
      "decisions": [],
      "evidence": {
        "mutation": {
          "score": 93.28,
          "killed": 111,
          "survived": 7,
          "mutants": 119,
          "files": 1
        },
        "e2e": {
          "cases": 6,
          "spec": "apps/sandbox-e2e/src/icon.spec.ts"
        },
        "pairs": 4,
        "baselines": 2,
        "cost": {
          "elements": 27,
          "depth": 4,
          "listeners": 0,
          "renders": 1,
          "micros": 18005,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "avatar",
      "classes": [
        "PctAvatar"
      ],
      "role": "the picture beside a name, and never the name itself",
      "category": "Data & status",
      "status": "released",
      "entrypoint": "@pacit/components/avatar",
      "selectors": [
        "pct-avatar"
      ],
      "harnesses": [
        "PctAvatarHarness"
      ],
      "docs": "/components/avatar",
      "summary": "The picture beside a name, with initials and then a silhouette standing in when there is no picture.",
      "usage": {
        "code": "<pct-avatar name=\"Ada Lovelace\" />",
        "lang": "html"
      },
      "examples": [
        {
          "key": "sizes",
          "title": "Sizes, and what fills them",
          "code": "import { Component } from '@angular/core';\nimport { PctAvatar } from '@pacit/components/avatar';\n\n/**\n * Sizes, and what fills them\n *\n * A `name` becomes initials; a `src` becomes the picture; neither becomes a silhouette.\n * The three sizes ride the library's own axis, so an avatar beside a field lines up.\n */\n@Component({\n  selector: 'demo-avatar-sizes',\n  imports: [PctAvatar],\n  styles:\n    ':host { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }',\n  template: `\n    <pct-avatar name=\"Ada Lovelace\" size=\"sm\" />\n    <pct-avatar name=\"Grace Hopper\" />\n    <pct-avatar name=\"Margaret Hamilton\" size=\"lg\" />\n    <pct-avatar size=\"lg\" />\n  `,\n})\nexport class AvatarSizesDemo {}"
        }
      ],
      "api": [
        {
          "name": "PctAvatar",
          "kind": "component",
          "selector": "pct-avatar",
          "description": "An avatar: the picture beside a name — and never the name itself.",
          "members": [
            {
              "name": "name",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The name the initials are taken from. It is a SOURCE FOR A DRAWING, not an accessible name — the host is hidden, and the name a user hears belongs to the text or the control beside the picture (0052).",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "src",
              "kind": "input",
              "required": false,
              "type": "string",
              "default": "''",
              "description": "The picture's URL. Empty means none — the initials (or the silhouette) stand instead.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "size",
              "kind": "input",
              "required": false,
              "type": "PctSize",
              "default": "config.defaultSize",
              "description": "Size on the shared control axis; from the global configuration by default.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-avatar",
              "note": ""
            },
            {
              "attribute": "aria-hidden",
              "kind": "static",
              "bound": "true",
              "note": "Decoration beside the meaning, like the icon it may fall back to drawing. Nothing below it can take focus and the one image is <code>alt=&quot;&quot;</code>, so hiding the subtree costs a reader nothing and saves them hearing a name twice (<code>req-a11y-built-in</code>, 0052)."
            },
            {
              "attribute": "data-pct-size",
              "kind": "attribute",
              "bound": "size()",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctAvatar",
          "kind": "component",
          "selector": "pct-avatar",
          "detail": null,
          "description": "An avatar: the picture beside a name — and never the name itself.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "image",
          "description": "the picture, when a source is given"
        },
        {
          "name": "initials",
          "description": "the letters drawn from the name"
        },
        {
          "name": "silhouette",
          "description": "the placeholder when there is neither"
        }
      ],
      "tokens": [
        {
          "name": "--pct-avatar-bg",
          "type": "color",
          "ref": "{pct.surface-100}",
          "light": "#f1f5f9",
          "dark": "#1e293b",
          "description": "Background of the avatar."
        },
        {
          "name": "--pct-avatar-border",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Edge colour of the avatar."
        },
        {
          "name": "--pct-avatar-fg",
          "type": "color",
          "ref": "{pct.text}",
          "light": "#0f172a",
          "dark": "#ffffff",
          "description": "Text colour of the initials."
        },
        {
          "name": "--pct-avatar-font-size",
          "type": "dimension",
          "ref": "{pct.font.size.md}",
          "light": "14px",
          "dark": "14px",
          "description": "Font size of the avatar."
        },
        {
          "name": "--pct-avatar-font-size-lg",
          "type": "dimension",
          "ref": "{pct.font.size.lg}",
          "light": "16px",
          "dark": "16px",
          "description": "Font size of the avatar at size lg."
        },
        {
          "name": "--pct-avatar-font-size-sm",
          "type": "dimension",
          "ref": "{pct.font.size.sm}",
          "light": "13px",
          "dark": "13px",
          "description": "Font size of the avatar at size sm."
        },
        {
          "name": "--pct-avatar-icon-fg",
          "type": "color",
          "ref": "{pct.text-muted}",
          "light": "#334155",
          "dark": "#e2e8f0",
          "description": "Text colour of the icon."
        },
        {
          "name": "--pct-avatar-radius",
          "type": "dimension",
          "ref": null,
          "light": "999px",
          "dark": "999px",
          "description": "Corner radius of the avatar."
        },
        {
          "name": "--pct-avatar-size",
          "type": "dimension",
          "ref": "{pct.control.height.md}",
          "light": "36px",
          "dark": "36px",
          "description": "Size of the avatar."
        },
        {
          "name": "--pct-avatar-size-lg",
          "type": "dimension",
          "ref": "{pct.control.height.lg}",
          "light": "44px",
          "dark": "44px",
          "description": "Size of the avatar at size lg."
        },
        {
          "name": "--pct-avatar-size-sm",
          "type": "dimension",
          "ref": "{pct.control.height.sm}",
          "light": "28px",
          "dark": "28px",
          "description": "Size of the avatar at size sm."
        }
      ],
      "texts": [],
      "keyboard": "| key | effect | test |\n| --- | ------ | ---- |\n|     |        |      |\n\nEmpty **by design**: nothing here can be landed on at all. The subtree is hidden from the\naccessibility tree, so anything focusable in it would be axe's `aria-hidden-focus` —\n`apps/sandbox-e2e/src/avatar.spec.ts` asks every element in the component to focus itself\nand counts zero, in three engines, and `tools/check-aria.mjs` point 8 refuses the general\ncase at build time ([`req-api-platform`](../requirements/api.md#req-api-platform)).",
      "decisions": [],
      "evidence": {
        "mutation": {
          "score": 87.5,
          "killed": 28,
          "survived": 3,
          "mutants": 32,
          "files": 1
        },
        "e2e": {
          "cases": 8,
          "spec": "apps/sandbox-e2e/src/avatar.spec.ts"
        },
        "pairs": 3,
        "baselines": 2,
        "cost": {
          "elements": 6,
          "depth": 2,
          "listeners": 0,
          "renders": 1,
          "micros": 4586,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "skeleton",
      "classes": [
        "PctSkeleton"
      ],
      "role": "the shape of content that has not arrived",
      "category": "Data & status",
      "status": "released",
      "entrypoint": "@pacit/components/skeleton",
      "selectors": [
        "pct-skeleton"
      ],
      "harnesses": [
        "PctSkeletonHarness"
      ],
      "docs": "/components/skeleton",
      "summary": "The shape of content that has not arrived, holding the room it is going to take.",
      "usage": {
        "code": "<pct-skeleton [lines]=\"3\" />",
        "lang": "html"
      },
      "examples": [
        {
          "key": "shapes",
          "title": "Shapes",
          "code": "import { Component } from '@angular/core';\nimport { PctSkeleton } from '@pacit/components/skeleton';\n\n/**\n * Shapes\n *\n * `shape=\"text\"` draws lines with a shorter last one, the way a paragraph ends; `\"block\"`\n * holds a box — an image, a card — at whatever size the container gives it; `\"circle\"` holds\n * a disc, sized on either axis, with the other following.\n */\n@Component({\n  selector: 'demo-skeleton-shapes',\n  imports: [PctSkeleton],\n  // A width, not only a ceiling — see `skeleton.demo.ts` (`lesson-160`).\n  styles: `\n    :host {\n      display: grid;\n      gap: 16px;\n      inline-size: min(28rem, 100%);\n    }\n    .block {\n      block-size: 7rem;\n    }\n    .disc {\n      block-size: var(--pct-avatar-size-lg);\n    }\n  `,\n  template: `\n    <pct-skeleton shape=\"block\" class=\"block\" />\n    <pct-skeleton shape=\"circle\" class=\"disc\" />\n    <pct-skeleton [lines]=\"4\" />\n  `,\n})\nexport class SkeletonShapesDemo {}"
        },
        {
          "key": "card",
          "title": "A card that is still coming",
          "code": "import { Component } from '@angular/core';\nimport { PctSkeleton } from '@pacit/components/skeleton';\n\n/**\n * A card that is still coming\n *\n * The three shapes arranged the way an application arranges them: a disc the avatar's own\n * size, one short line where the name will be, three where the text will be — under ONE busy\n * region. The wait is a fact about the card, not about each placeholder inside it.\n */\n@Component({\n  selector: 'demo-skeleton-card',\n  imports: [PctSkeleton],\n  styles: `\n    :host {\n      display: block;\n      inline-size: min(24rem, 100%);\n    }\n    .card {\n      display: flex;\n      gap: 16px;\n      padding: 16px;\n      border: 1px solid var(--pct-border);\n      border-radius: var(--pct-radius-md);\n    }\n    .avatar {\n      block-size: var(--pct-avatar-size);\n    }\n    .body {\n      display: grid;\n      flex: 1;\n      gap: 8px;\n    }\n    .name {\n      inline-size: 55%;\n    }\n  `,\n  template: `\n    <div class=\"card\" aria-busy=\"true\">\n      <pct-skeleton shape=\"circle\" class=\"avatar\" />\n      <div class=\"body\">\n        <pct-skeleton class=\"name\" />\n        <pct-skeleton [lines]=\"3\" />\n      </div>\n    </div>\n  `,\n})\nexport class SkeletonCardDemo {}"
        }
      ],
      "api": [
        {
          "name": "PctSkeleton",
          "kind": "component",
          "selector": "pct-skeleton",
          "description": "A skeleton: the shape of content that has not arrived, drawn where the content will be.",
          "members": [
            {
              "name": "lines",
              "kind": "input",
              "required": false,
              "type": "number",
              "default": "1",
              "description": "How many lines of the surrounding text this stands in for — one by default.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "shape",
              "kind": "input",
              "required": false,
              "type": "PctSkeletonShape",
              "default": "'text'",
              "description": "Lines of text, one box, or one disc — text by default.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "paused",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Stop the sheen where it stands — false by default, and the default is the promise.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-skeleton",
              "note": ""
            },
            {
              "attribute": "aria-hidden",
              "kind": "static",
              "bound": "true",
              "note": "The component draws and does not speak. Everything below it is a box with a colour, there is no text anywhere in the template and nothing in it can take focus, so hiding the subtree costs a reader nothing and saves them a description of content that does not exist (<code>req-a11y-built-in</code>, 0050). What a reader needs to hear is <code>aria-busy</code> on the region, and the region is not ours to write on."
            },
            {
              "attribute": "data-pct-shape",
              "kind": "attribute",
              "bound": "shape()",
              "note": ""
            },
            {
              "attribute": "data-pct-paused",
              "kind": "attribute",
              "bound": "paused() ? \"\" : null",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctSkeletonShape",
          "kind": "type",
          "selector": null,
          "detail": "'text' | 'block' | 'circle'",
          "description": "Which drawing a skeleton is: the lines of a paragraph, one box, or one disc.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctSkeleton",
          "kind": "component",
          "selector": "pct-skeleton",
          "detail": null,
          "description": "A skeleton: the shape of content that has not arrived, drawn where the content will be.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [
        {
          "name": "fill",
          "description": "the shimmer that travels it"
        },
        {
          "name": "track",
          "description": "one line of the shape"
        }
      ],
      "tokens": [
        {
          "name": "--pct-skeleton-fill-bg",
          "type": "color",
          "ref": "{pct.surface}",
          "light": "#ffffff",
          "dark": "#0f172a",
          "description": "Colour of the shimmer."
        },
        {
          "name": "--pct-skeleton-fill-blur",
          "type": "dimension",
          "ref": null,
          "light": "6px",
          "dark": "6px",
          "description": "Blur radius of the shimmer."
        },
        {
          "name": "--pct-skeleton-fill-size",
          "type": "dimension",
          "ref": null,
          "light": "100%",
          "dark": "100%",
          "description": "Width of the shimmer."
        },
        {
          "name": "--pct-skeleton-track-bg",
          "type": "color",
          "ref": "{pct.border}",
          "light": "#e2e8f0",
          "dark": "#334155",
          "description": "Background of the track."
        },
        {
          "name": "--pct-skeleton-track-height",
          "type": "dimension",
          "ref": null,
          "light": "1cap",
          "dark": "1cap",
          "description": "Height of the track."
        },
        {
          "name": "--pct-skeleton-track-radius",
          "type": "dimension",
          "ref": "{pct.radius.md}",
          "light": "8px",
          "dark": "8px",
          "description": "Corner radius of the track."
        },
        {
          "name": "--pct-skeleton-track-width-last",
          "type": "dimension",
          "ref": null,
          "light": "60%",
          "dark": "60%",
          "description": "Width of the track for the last line."
        }
      ],
      "texts": [],
      "keyboard": "| key | effect | test |\n| --- | ------ | ---- |\n|     |        |      |\n\nEmpty **by design**, and for the strongest reason in the library so far: nothing here can be\nlanded on at all. The subtree is hidden from the accessibility tree, so anything focusable in\nit would be axe's `aria-hidden-focus` — `apps/sandbox-e2e/src/skeleton.spec.ts` asks every\nelement in the component to focus itself and counts zero, in three engines, and\n`tools/check-aria.mjs` point 8 refuses the general case at build time\n([`req-api-platform`](../requirements/api.md#req-api-platform)).",
      "decisions": [],
      "evidence": {
        "mutation": {
          "score": 92.59,
          "killed": 25,
          "survived": 2,
          "mutants": 27,
          "files": 1
        },
        "e2e": {
          "cases": 14,
          "spec": "apps/sandbox-e2e/src/skeleton.spec.ts"
        },
        "pairs": 2,
        "baselines": 2,
        "cost": {
          "elements": 10,
          "depth": 4,
          "listeners": 1,
          "renders": 1,
          "micros": 7317,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "container",
      "classes": [
        "PctContainer"
      ],
      "role": "a reading column capped by a token",
      "category": "Layout & theming",
      "status": "released",
      "entrypoint": "@pacit/components/container",
      "selectors": [
        "pct-container"
      ],
      "harnesses": [
        "PctContainerHarness"
      ],
      "docs": "/components/container",
      "summary": "A reading column: content capped at a maximum width, centred, with a gutter that follows the viewport.",
      "usage": {
        "code": "<pct-container>\n  <p>A column no wider than the reading measure.</p>\n</pct-container>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "width",
          "title": "A narrower column, per instance",
          "code": "import { Component } from '@angular/core';\nimport { PctContainer } from '@pacit/components/container';\n\n/**\n * A narrower column, per instance\n *\n * The cap is a token, and a token is an input: override `--pct-container-max-width` on one\n * element and only that column narrows — no class, no media query.\n */\n@Component({\n  selector: 'demo-container-width',\n  imports: [PctContainer],\n  styles:\n    ':host { display: grid; gap: 12px; inline-size: 100%; } pct-container { outline: 1px dashed currentColor; }',\n  // Each column sits in a block-flow wrapper: as a grid or flex item, a column's auto\n  // margins absorb the free space and its containment leaves it no width of its own\n  // (lesson-146) — the wrapper is the item, the column is a block inside it.\n  template: `\n    <div>\n      <pct-container style=\"--pct-container-max-width: 20rem\">\n        <p>Twenty rem, for a form.</p>\n      </pct-container>\n    </div>\n    <div>\n      <pct-container style=\"--pct-container-max-width: 32rem\">\n        <p>Thirty-two rem, for an article.</p>\n      </pct-container>\n    </div>\n  `,\n})\nexport class ContainerWidthDemo {}"
        }
      ],
      "api": [
        {
          "name": "PctContainer",
          "kind": "component",
          "selector": "pct-container",
          "description": "A reading column: content capped at --pct-container-max-width, centred, with a gutter that follows the viewport. The first of the three layout primitives (0057), and like the other two it carries no ARIA at all — layout is presentational, and the platform's div needs no help being one. It has no inputs either: both of its lengths are tokens, and a token scoped on the element is the per-instance API.",
          "members": [],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-container",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctContainer",
          "kind": "component",
          "selector": "pct-container",
          "detail": null,
          "description": "A reading column: content capped at --pct-container-max-width, centred, with a gutter that follows the viewport. The first of the three layout primitives (0057), and like the other two it carries no ARIA at all — layout is presentational, and the platform's div needs no help being one. It has no inputs either: both of its lengths are tokens, and a token scoped on the element is the per-instance API.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [],
      "tokens": [
        {
          "name": "--pct-container-max-width",
          "type": "dimension",
          "ref": null,
          "light": "72rem",
          "dark": "72rem",
          "description": "Maximum width of the column."
        },
        {
          "name": "--pct-container-padding-x",
          "type": "dimension",
          "ref": null,
          "light": "clamp(16px, 4vw, 40px)",
          "dark": "clamp(16px, 4vw, 40px)",
          "description": "Horizontal padding of the column."
        }
      ],
      "texts": [],
      "keyboard": "| key | effect | test |\n| --- | ------ | ---- |\n|     |        |      |\n\nEmpty **by design**: the container is not interactive and adds nothing focusable. The\nprojected content keeps its own keyboard, untouched — there is no handler in the component\nto intercept anything with.",
      "decisions": [],
      "evidence": {
        "mutation": null,
        "e2e": {
          "cases": 7,
          "spec": "apps/sandbox-e2e/src/layout.spec.ts"
        },
        "pairs": 0,
        "baselines": 0,
        "cost": {
          "elements": 2,
          "depth": 2,
          "listeners": 0,
          "renders": 1,
          "micros": 1538,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "grid",
      "classes": [
        "PctGrid"
      ],
      "role": "a grid that finds its own column count",
      "category": "Layout & theming",
      "status": "released",
      "entrypoint": "@pacit/components/grid",
      "selectors": [
        "pct-grid"
      ],
      "harnesses": [
        "PctGridHarness"
      ],
      "docs": "/components/grid",
      "summary": "A grid that finds its own column count from the narrowest a card may get, with no media query to write.",
      "usage": {
        "code": "<pct-grid>\n  <div>One</div>\n  <div>Two</div>\n  <div>Three</div>\n</pct-grid>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "width",
          "title": "Cards that decide their own count",
          "code": "import { Component } from '@angular/core';\nimport { PctGrid } from '@pacit/components/grid';\n\n/**\n * Cards that decide their own count\n *\n * `--pct-grid-min-width` is the smallest a cell may be; the grid fits as many as the\n * container allows and wraps the rest. No breakpoints — the container is the question.\n */\n@Component({\n  selector: 'demo-grid-width',\n  imports: [PctGrid],\n  // A definite width, or `auto-fit` repeats once (lesson-146).\n  styles:\n    ':host { display: block; inline-size: 100%; } div { padding: 12px; border: 1px solid currentColor; border-radius: 8px; }',\n  template: `\n    <pct-grid style=\"--pct-grid-min-width: 11rem; --pct-grid-gap: 12px\">\n      <div>Container</div>\n      <div>Stack</div>\n      <div>Grid</div>\n      <div>Theme</div>\n      <div>Button</div>\n      <div>Tabs</div>\n    </pct-grid>\n  `,\n})\nexport class GridWidthDemo {}"
        }
      ],
      "api": [
        {
          "name": "PctGrid",
          "kind": "component",
          "selector": "pct-grid",
          "description": "A grid of equals that finds its own column count. The consumer states the one thing only they know — how narrow a card may get, --pct-grid-min-width — and the browser derives every \"breakpoint\" from it: columns drop one by one as the space runs out, down to a single column that never overflows. Responsive without a media query in consumer code, which is the third layout primitive's whole pitch (0057).",
          "members": [],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-grid",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctGrid",
          "kind": "component",
          "selector": "pct-grid",
          "detail": null,
          "description": "A grid of equals that finds its own column count. The consumer states the one thing only they know — how narrow a card may get, --pct-grid-min-width — and the browser derives every \"breakpoint\" from it: columns drop one by one as the space runs out, down to a single column that never overflows. Responsive without a media query in consumer code, which is the third layout primitive's whole pitch (0057).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [],
      "tokens": [
        {
          "name": "--pct-grid-gap",
          "type": "dimension",
          "ref": "{pct.space.5}",
          "light": "16px",
          "dark": "16px",
          "description": "Gap between the grid's cells."
        },
        {
          "name": "--pct-grid-min-width",
          "type": "dimension",
          "ref": null,
          "light": "16rem",
          "dark": "16rem",
          "description": "Minimum width of a cell before the grid wraps."
        }
      ],
      "texts": [],
      "keyboard": "| key | effect | test |\n| --- | ------ | ---- |\n|     |        |      |\n\nEmpty **by design**: not interactive, nothing focusable added. The cards keep their own\nkeyboard, in DOM order — which is also the visual order, because the grid never reorders.",
      "decisions": [],
      "evidence": {
        "mutation": null,
        "e2e": {
          "cases": 7,
          "spec": "apps/sandbox-e2e/src/layout.spec.ts"
        },
        "pairs": 0,
        "baselines": 0,
        "cost": {
          "elements": 7,
          "depth": 2,
          "listeners": 0,
          "renders": 1,
          "micros": 2051,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "stack",
      "classes": [
        "PctStack"
      ],
      "role": "the space between blocks, from the scale",
      "category": "Layout & theming",
      "status": "released",
      "entrypoint": "@pacit/components/stack",
      "selectors": [
        "pct-stack"
      ],
      "harnesses": [
        "PctStackHarness"
      ],
      "docs": "/components/stack",
      "summary": "A column of blocks standing one chosen step apart, the step taken from the spacing scale.",
      "usage": {
        "code": "<pct-stack gap=\"lg\">\n  <div>First</div>\n  <div>Second</div>\n</pct-stack>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "gaps",
          "title": "Three rhythms",
          "code": "import { Component } from '@angular/core';\nimport { PctStack } from '@pacit/components/stack';\n\n/**\n * Three rhythms\n *\n * `gap` rides the library's shared size axis — sm, md, lg — so the space between a form's\n * rows is the same decision as the space between a page's sections, taken once.\n */\n@Component({\n  selector: 'demo-stack-gaps',\n  imports: [PctStack],\n  // The width is stated, not shrink-wrapped by the stage (lesson-146).\n  styles:\n    ':host { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; inline-size: 100%; } div { padding: 6px 10px; border: 1px solid currentColor; border-radius: 6px; }',\n  template: `\n    <pct-stack gap=\"sm\"\n      ><div>sm</div>\n      <div>sm</div>\n      <div>sm</div></pct-stack\n    >\n    <pct-stack gap=\"md\"\n      ><div>md</div>\n      <div>md</div>\n      <div>md</div></pct-stack\n    >\n    <pct-stack gap=\"lg\"\n      ><div>lg</div>\n      <div>lg</div>\n      <div>lg</div></pct-stack\n    >\n  `,\n})\nexport class StackGapsDemo {}"
        }
      ],
      "api": [
        {
          "name": "PctStack",
          "kind": "component",
          "selector": "pct-stack",
          "description": "Vertical rhythm: a flex column whose blocks stand one chosen step apart, instead of each block bringing its own margin and the space between two of them being their sum. The second layout primitive (0057): no ARIA, no parts, one input.",
          "members": [
            {
              "name": "gap",
              "kind": "input",
              "required": false,
              "type": "PctSize",
              "default": "'md'",
              "description": "The step of the spacing scale between blocks.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-stack",
              "note": ""
            },
            {
              "attribute": "data-pct-gap",
              "kind": "attribute",
              "bound": "gap()",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctStack",
          "kind": "component",
          "selector": "pct-stack",
          "detail": null,
          "description": "Vertical rhythm: a flex column whose blocks stand one chosen step apart, instead of each block bringing its own margin and the space between two of them being their sum. The second layout primitive (0057): no ARIA, no parts, one input.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [],
      "tokens": [
        {
          "name": "--pct-stack-gap",
          "type": "dimension",
          "ref": "{pct.space.5}",
          "light": "16px",
          "dark": "16px",
          "description": "Gap between the stack's children."
        },
        {
          "name": "--pct-stack-gap-lg",
          "type": "dimension",
          "ref": "{pct.space.6}",
          "light": "24px",
          "dark": "24px",
          "description": "Gap between the stack's children at size lg."
        },
        {
          "name": "--pct-stack-gap-sm",
          "type": "dimension",
          "ref": "{pct.space.3}",
          "light": "8px",
          "dark": "8px",
          "description": "Gap between the stack's children at size sm."
        }
      ],
      "texts": [],
      "keyboard": "| key | effect | test |\n| --- | ------ | ---- |\n|     |        |      |\n\nEmpty **by design**: not interactive, nothing focusable added, no handler anywhere.",
      "decisions": [],
      "evidence": {
        "mutation": {
          "score": 100,
          "killed": 1,
          "survived": 0,
          "mutants": 1,
          "files": 1
        },
        "e2e": {
          "cases": 7,
          "spec": "apps/sandbox-e2e/src/layout.spec.ts"
        },
        "pairs": 0,
        "baselines": 0,
        "cost": {
          "elements": 4,
          "depth": 2,
          "listeners": 0,
          "renders": 1,
          "micros": 1375,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "hero",
      "classes": [
        "PctHero"
      ],
      "role": "the brand gradient as equipment",
      "category": "Layout & theming",
      "status": "released",
      "entrypoint": "@pacit/components/hero",
      "selectors": [
        "[pctHero]"
      ],
      "harnesses": [
        "PctHeroHarness"
      ],
      "docs": "/components/hero",
      "summary": "The library's one loud face, on the element that is already there: a rim, a word or a surface.",
      "usage": {
        "code": "<article pctHero=\"edge\" show=\"interact\">…</article>\n<h3 pctHero=\"text\">Tabs</h3>\n<div [pctHero]=\"featured() ? 'fill' : null\">…</div>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "interact",
          "title": "When it paints",
          "code": "import { Component } from '@angular/core';\nimport { PctHero } from '@pacit/components/hero';\n\n/**\n * When it paints\n *\n * `show=\"interact\"` is hover OR `:focus-visible`, never hover alone — a treatment only a\n * mouse can summon is one a keyboard cannot, and this library does not ship those. On a\n * device with no hover the pair falls to focus and a press. Tab to the card to see it.\n */\n@Component({\n  selector: 'demo-hero-interact',\n  imports: [PctHero],\n  // The class is `face` and not `card`, measured: the gallery's own overflow check walks\n  // `document.querySelectorAll('.card')` across the whole page, so a demo that spells its\n  // box `card` hands that check a tile with no stage and no name inside it.\n  styles: `\n    :host {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 16px;\n      align-items: center;\n    }\n\n    .face {\n      box-sizing: border-box;\n      display: block;\n      padding: 12px 16px;\n      border: 1px solid var(--pct-border);\n      border-radius: var(--pct-radius-lg);\n      background: var(--pct-surface);\n      color: inherit;\n      text-decoration: none;\n    }\n  `,\n  template: `\n    <a class=\"face\" href=\"#hero\" pctHero=\"edge\" show=\"interact\">\n      Under attention\n    </a>\n    <a class=\"face\" href=\"#hero\" pctHero=\"text\" show=\"interact\">\n      Or a word of it\n    </a>\n  `,\n})\nexport class HeroInteractDemo {}"
        }
      ],
      "api": [
        {
          "name": "PctHero",
          "kind": "component",
          "selector": "[pctHero]",
          "description": "The brand gradient as equipment — an attribute component on the element the consumer already has, exactly as pctButton dresses a <button> (0065).",
          "members": [
            {
              "name": "pctHero",
              "kind": "input",
              "required": false,
              "type": "PctHeroFace | null",
              "default": "null",
              "description": "The face, or null for none — so a condition needs no API of its own: the input takes a binding, and a signal already says what a second input would have said again.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "show",
              "kind": "input",
              "required": false,
              "type": "PctHeroShow",
              "default": "'always'",
              "description": "always, or only under hover and :focus-visible — never hover alone, because a treatment only a mouse can summon is one a keyboard cannot.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            },
            {
              "name": "paused",
              "kind": "input",
              "required": false,
              "type": "boolean",
              "default": "false",
              "description": "Stops the sweep where it stands, for a page that wants it stopped.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "class",
              "kind": "static",
              "bound": "pct-hero",
              "note": ""
            },
            {
              "attribute": "data-pct-hero",
              "kind": "attribute",
              "bound": "pctHero()",
              "note": ""
            },
            {
              "attribute": "data-pct-show",
              "kind": "attribute",
              "bound": "pctHero() ? show() : null",
              "note": ""
            },
            {
              "attribute": "data-pct-paused",
              "kind": "attribute",
              "bound": "paused() ? \"\" : null",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctHero",
          "kind": "component",
          "selector": "[pctHero]",
          "detail": null,
          "description": "The brand gradient as equipment — an attribute component on the element the consumer already has, exactly as pctButton dresses a <button> (0065).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctHeroFace",
          "kind": "type",
          "selector": null,
          "detail": "'edge' | 'text' | 'fill'",
          "description": "Which surface of the element takes the brand gradient. One face per element and not three switches: gradient text on a gradient fill is the one combination nobody can read, and a single input refuses it by construction rather than by documentation (0065).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctHeroShow",
          "kind": "type",
          "selector": null,
          "detail": "'always' | 'interact'",
          "description": "When the face paints: always, or only while the element is under attention.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [],
      "tokens": [],
      "texts": [],
      "keyboard": "| key | effect | test |\n| --- | ------ | ---- |\n|     |        |      |\n\nEmpty **by design**: the face takes no focus and answers no key. `show=\"interact\"` reacts to\n`:focus-visible` on the element the consumer made focusable — the treatment follows attention,\nit does not create it.",
      "decisions": [],
      "evidence": {
        "mutation": {
          "score": 100,
          "killed": 2,
          "survived": 0,
          "mutants": 2,
          "files": 1
        },
        "e2e": {
          "cases": 8,
          "spec": "apps/sandbox-e2e/src/hero.spec.ts"
        },
        "pairs": 6,
        "baselines": 1,
        "cost": {
          "elements": 3,
          "depth": 1,
          "listeners": 0,
          "renders": 1,
          "micros": 3431,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    },
    {
      "id": "theme",
      "classes": [
        "PctTheme"
      ],
      "role": "the theme, spelled from a template",
      "category": "Layout & theming",
      "status": "released",
      "entrypoint": "@pacit/components/theme",
      "selectors": [
        "[pctTheme]"
      ],
      "harnesses": [
        "PctThemeHarness"
      ],
      "docs": "/components/theme",
      "summary": "The theme as an attribute: light, dark, or the system's own answer.",
      "usage": {
        "code": "<section pctTheme=\"dark\">\n  <p>A dark island: every token below it flips.</p>\n</section>",
        "lang": "html"
      },
      "examples": [
        {
          "key": "islands",
          "title": "An island that switches",
          "code": "import { Component, signal } from '@angular/core';\nimport { PctButton } from '@pacit/components/button';\nimport { PctTheme, PctThemeName } from '@pacit/components/theme';\n\n/**\n * An island that switches\n *\n * The directive writes one attribute; the tokens under it do the rest. Bind it to a signal\n * and a whole region of the page changes theme without touching the document.\n */\n@Component({\n  selector: 'demo-theme-islands',\n  imports: [PctButton, PctTheme],\n  styles:\n    ':host { display: grid; gap: 12px; } section { padding: 16px; border-radius: 12px; background: var(--pct-surface); color: var(--pct-text); border: 1px solid var(--pct-border); }',\n  template: `\n    <section [pctTheme]=\"island()\">\n      <p>This island is {{ island() }}.</p>\n      <button pctButton variant=\"soft\" size=\"sm\" (click)=\"flip()\">\n        Flip the island\n      </button>\n    </section>\n  `,\n})\nexport class ThemeIslandsDemo {\n  readonly island = signal<PctThemeName>('dark');\n\n  protected flip(): void {\n    this.island.update((t) => (t === 'dark' ? 'light' : 'dark'));\n  }\n}"
        }
      ],
      "api": [
        {
          "name": "PctTheme",
          "kind": "directive",
          "selector": "[pctTheme]",
          "description": "Theme as a template binding: [pctTheme]=\"'dark'\" writes data-theme=\"dark\" on the host — the attribute the generated skin keys its theme blocks on — and null removes it, so the system preference speaks again (req-token-system).",
          "members": [
            {
              "name": "pctTheme",
              "kind": "input",
              "required": true,
              "type": "PctThemeName | null",
              "default": null,
              "description": "The theme to pin on this subtree, or null to follow the page.",
              "since": "0.1.0",
              "unreleased": false,
              "deprecated": null
            }
          ],
          "host": [
            {
              "attribute": "data-theme",
              "kind": "attribute",
              "bound": "pctTheme()",
              "note": ""
            }
          ]
        }
      ],
      "exports": [
        {
          "name": "PctThemeName",
          "kind": "type",
          "selector": null,
          "detail": "'light' | 'dark'",
          "description": "The two themes the shipped skin keys its blocks on.",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        },
        {
          "name": "PctTheme",
          "kind": "directive",
          "selector": "[pctTheme]",
          "detail": null,
          "description": "Theme as a template binding: [pctTheme]=\"'dark'\" writes data-theme=\"dark\" on the host — the attribute the generated skin keys its theme blocks on — and null removes it, so the system preference speaks again (req-token-system).",
          "since": "0.1.0",
          "deprecated": null,
          "unreleased": false
        }
      ],
      "parts": [],
      "tokens": [],
      "texts": [],
      "keyboard": "| key | effect | test |\n| --- | ------ | ---- |\n|     |        |      |\n\nEmpty **by design**: not interactive, adds nothing focusable, intercepts nothing.",
      "decisions": [],
      "evidence": {
        "mutation": null,
        "e2e": {
          "cases": 4,
          "spec": "apps/sandbox-e2e/src/theme.spec.ts"
        },
        "pairs": 0,
        "baselines": 0,
        "cost": {
          "elements": 4,
          "depth": 3,
          "listeners": 0,
          "renders": 1,
          "micros": 2704,
          "measured": "2026-10-31",
          "machine": "Intel(R) Core(TM) i7-3612QM CPU @ 2.10GHz"
        }
      }
    }
  ]
}
