Phase 4: component library

The approved element rules and ornament kit as Astro components in
packages/ui: buttons, pills, chips, filters, kickers, notices, fields and
cards; star paths, the header divider, tick scales and rings, seals and
edge seals, gilt corners, dust and placeholder art; the site header,
footer and page shell. Brand fonts are self-hosted. A gallery page shows
everything, and astro check now runs with the root typecheck.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
This commit is contained in:
Claude
2026-10-11 20:44:16 +00:00
parent 32cf13a5cb
commit 807528a613
159 changed files with 6270 additions and 87 deletions
+338
View File
@@ -0,0 +1,338 @@
/*
* The element rules Greg approved on 2026-10-11 (Element rules board, canvas
* version 17). Classes, not only components, so Markdown and the admin page
* can use them too.
*
* Gold is structure (frames, rules, active and favorite), teal is the action
* or a live state, violet only marks a star. Hover and focus changes take
* 150 to 200ms and never move text. Disabled is 40% opacity with no hover.
*/
/* I. Buttons: teal pill primary, gold hairline secondary, filament text link. */
.gk-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
min-height: var(--gk-control);
padding: 0 24px;
border-radius: var(--gk-radius-pill);
font-family: var(--gk-font-body);
font-size: 14px;
font-weight: 700;
line-height: 1.2;
text-decoration: none;
white-space: nowrap;
cursor: pointer;
transition: background var(--gk-ease), border-color var(--gk-ease), box-shadow var(--gk-ease), color var(--gk-ease);
}
.gk-btn.gk-sm {
min-height: var(--gk-control-sm);
padding: 0 16px;
font-size: 13px;
}
.gk-pri {
background: var(--gk-teal);
color: var(--gk-sky);
border: 0;
}
.gk-pri:hover {
background: var(--gk-teal-bright);
color: var(--gk-sky);
box-shadow: 0 0 0 4px rgba(43, 194, 183, 0.16);
}
.gk-sec {
background: transparent;
color: var(--gk-text);
border: 1px solid var(--gk-gold-line);
font-weight: 600;
}
.gk-sec:hover {
background: var(--gk-gold-tint);
border-color: var(--gk-gold);
color: var(--gk-text);
}
.gk-btn:disabled,
.gk-btn[aria-disabled="true"] {
opacity: 0.4;
pointer-events: none;
}
.gk-text {
color: var(--gk-teal-bright);
font-weight: 600;
text-decoration: none;
padding-bottom: 3px;
background: var(--gk-filament) no-repeat center bottom / 0 1.5px;
transition: background-size 0.2s, color var(--gk-ease);
}
.gk-text:hover {
background-size: 100% 1.5px;
color: var(--gk-text);
}
.gk-text.gk-quiet {
color: var(--gk-soft);
font-weight: 500;
}
.gk-text.gk-quiet:hover {
color: var(--gk-text);
}
/* II. Round pill is a state; square chip is a label. */
.gk-pill {
display: inline-flex;
align-items: center;
gap: 6px;
height: var(--gk-chip);
padding: 0 11px;
border-radius: var(--gk-radius-pill);
border: 1px solid var(--gk-lines-strong);
color: var(--gk-soft);
font-size: 12px;
font-weight: 600;
white-space: nowrap;
}
.gk-pill.live {
border-color: rgba(43, 194, 183, 0.6);
color: var(--gk-teal-bright);
}
.gk-pill.live::before {
content: "";
flex: none;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--gk-teal-bright);
box-shadow: 0 0 6px rgba(43, 194, 183, 0.8);
}
/* A state that is not happening now keeps the round pill without the lit dot. */
.gk-pill.state {
border-color: rgba(43, 194, 183, 0.6);
color: var(--gk-teal-bright);
}
.gk-pill.cur {
border-color: var(--gk-gold-line);
color: var(--gk-gold-bright);
}
.gk-chip {
display: inline-flex;
align-items: center;
gap: 6px;
min-height: var(--gk-chip);
padding: 0 10px;
border-radius: var(--gk-radius-chip);
border: 1px solid var(--gk-lines);
color: var(--gk-soft);
font-size: 12px;
font-weight: 600;
text-decoration: none;
transition: border-color var(--gk-ease), color var(--gk-ease);
}
.gk-chip.core {
border-color: rgba(43, 194, 183, 0.55);
color: var(--gk-text);
}
a.gk-chip:hover {
border-color: var(--gk-teal-bright);
color: var(--gk-text);
}
/* III. Filters: separate 36px pills; the active one gets gold and a star. */
.gk-filters {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 0;
padding: 0;
list-style: none;
}
.gk-filter {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
min-height: var(--gk-control-sm);
padding: 0 14px;
border-radius: var(--gk-radius-pill);
border: 1px solid var(--gk-lines);
background: transparent;
color: var(--gk-soft);
font-family: var(--gk-font-body);
font-size: 13px;
font-weight: 600;
text-decoration: none;
cursor: pointer;
transition: border-color var(--gk-ease), background var(--gk-ease), color var(--gk-ease);
}
.gk-filter:hover {
border-color: var(--gk-gold-line);
color: var(--gk-text);
}
.gk-filter.on,
.gk-filter[aria-current],
.gk-filter[aria-pressed="true"] {
border-color: var(--gk-gold);
background: var(--gk-gold-tint);
color: var(--gk-text);
}
.gk-filter .gk-fs {
display: none;
}
.gk-filter.on .gk-fs,
.gk-filter[aria-current] .gk-fs,
.gk-filter[aria-pressed="true"] .gk-fs {
display: inline-flex;
}
/* IV. Kickers are gold capitals over a tick scale; notices are a ledger note. */
.gk-kicker {
display: inline-flex;
flex-direction: column;
gap: 6px;
}
.gk-kicker-text {
display: inline-flex;
align-items: center;
gap: 8px;
color: var(--gk-gold-bright);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.3em;
text-transform: uppercase;
}
.gk-note {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
border-left: 2px solid var(--gk-gold);
border-radius: 0 var(--gk-radius) var(--gk-radius) 0;
background: rgba(201, 162, 74, 0.08);
color: var(--gk-text);
font-size: 13px;
}
.gk-note b {
font-weight: 600;
}
/* V. Fields: 10px box; on focus a teal border and a filament across the base. */
.gk-label {
display: flex;
flex-direction: column;
gap: 6px;
}
.gk-label-text {
font-size: 12px;
font-weight: 600;
color: var(--gk-soft);
}
.gk-hint {
font-size: 12px;
color: var(--gk-muted);
}
.gk-field {
width: 100%;
padding: 0 14px;
border: 1px solid var(--gk-lines);
border-radius: var(--gk-radius);
background: var(--gk-sky) var(--gk-filament) no-repeat center bottom / 0 2px;
color: var(--gk-text);
font-family: var(--gk-font-body);
font-size: 14px;
transition: border-color var(--gk-ease), background-size 0.25s;
}
input.gk-field,
select.gk-field {
min-height: var(--gk-control);
}
textarea.gk-field {
padding: 12px 14px;
min-height: 120px;
resize: vertical;
}
.gk-field::placeholder {
color: var(--gk-placeholder);
}
.gk-field:hover,
.gk-search:hover {
border-color: var(--gk-lines-strong);
}
.gk-field:focus {
outline: none;
border-color: var(--gk-teal-bright);
background-size: 92% 2px;
}
.gk-field[aria-invalid="true"] {
border-color: #f0857a;
}
.gk-search {
display: flex;
align-items: center;
gap: 8px;
padding: 0 14px;
min-height: var(--gk-control);
border: 1px solid var(--gk-lines);
border-radius: var(--gk-radius);
background: var(--gk-midnight) var(--gk-filament) no-repeat center bottom / 0 2px;
color: var(--gk-muted);
transition: border-color var(--gk-ease), background-size 0.25s;
}
.gk-search:focus-within {
border-color: var(--gk-teal-bright);
background-size: 92% 2px;
}
.gk-search input {
flex: 1;
min-width: 0;
min-height: 40px;
border: 0;
background: transparent;
color: var(--gk-text);
font: 14px var(--gk-font-body);
}
.gk-search input:focus {
outline: none;
}
.gk-search input::placeholder {
color: var(--gk-placeholder);
}
/* VI. Cards: a linked card turns gold and shows a tick scale on hover. */
.gk-card {
position: relative;
display: flex;
flex-direction: column;
gap: 6px;
padding: 16px;
border: 1px solid var(--gk-lines);
border-radius: var(--gk-radius);
background: var(--gk-midnight);
color: var(--gk-text);
text-decoration: none;
transition: border-color 0.2s;
}
.gk-card::before {
content: "";
position: absolute;
left: 16px;
right: 16px;
top: 0;
height: 7px;
background: url("../assets/ornaments/card-ticks.svg") repeat-x left top;
opacity: 0;
transition: opacity 0.2s;
pointer-events: none;
}
a.gk-card:hover,
.gk-card.gk-linked:hover {
border-color: rgba(201, 162, 74, 0.55);
}
a.gk-card:hover::before,
.gk-card.gk-linked:hover::before {
opacity: 1;
}
.gk-featured {
position: relative;
border: 1px solid rgba(201, 162, 74, 0.55);
border-radius: var(--gk-radius);
background: var(--gk-midnight);
}