The page held six teaching sections and a 142-line `styles:` block, at 471 effective lines against a limit of 250. It is now 36 lines of composition. Angular scopes a component's CSS to markup that component rendered, so the split had to move each rule to its owner. `concept-card` owns the card vocabulary and renders it. `.app-code`, `.app-lead`, `.app-cols` and `.app-note` become globals, because their targets are projected or arrive through `[innerHTML]`. That constraint exposed a live bug. The syntax-highlighting rules compiled to `pre[_ngcontent-%COMP%] .k[_ngcontent-%COMP%]`, but `highlight-ts` injects the `.k`/`.s`/`.c` spans through `[innerHTML]`, so they carry no scope attribute and the rule never matched. Keywords, strings and comments have always rendered in the plain foreground colour. The rules are global now, on five new `--app-code-*` tokens. Widen the colour guard while here: it scanned only `*.component.ts`, so every `*.page.ts`, `*.section.ts` and `*.step.ts` was invisible to it. That is how this page collected 21 hardcoded colours. One other file needed a fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
106 lines
2.9 KiB
TypeScript
106 lines
2.9 KiB
TypeScript
import { Component, input } from '@angular/core';
|
|
|
|
/** Molecule-shaped teaching card: the showcase's "before/after" box. Renders its own
|
|
tag pill and, when `code` is set, the highlighted snippet — everything else comes in
|
|
via `<ng-content />`. Angular does not style projected content from the receiving
|
|
component, so any other card-shaped block inside a section (a sub-label, a nested
|
|
ok/err result) nests another `<app-concept-card>` rather than writing raw `.tag`
|
|
markup — that markup would carry the SECTION's scope, not this component's, and the
|
|
rule here would never match it. */
|
|
@Component({
|
|
selector: 'app-concept-card',
|
|
imports: [],
|
|
styles: [
|
|
`
|
|
.card {
|
|
border: 1px solid var(--rhc-color-grijs-200);
|
|
border-radius: 10px;
|
|
padding: 1.25rem;
|
|
background: var(--rhc-color-wit);
|
|
}
|
|
.card--bad {
|
|
border-color: var(--rhc-color-rood-300);
|
|
}
|
|
.card--good {
|
|
border-color: var(--rhc-color-groen-300);
|
|
}
|
|
.tag {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
font-weight: 700;
|
|
font-size: 0.72rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
margin: 0 0 0.75rem;
|
|
}
|
|
.tag::before {
|
|
content: '';
|
|
width: 0.6rem;
|
|
height: 0.6rem;
|
|
border-radius: 50%;
|
|
}
|
|
.tag.bad {
|
|
color: var(--rhc-color-rood-600);
|
|
}
|
|
.tag.bad::before {
|
|
background: var(--rhc-color-rood-500);
|
|
}
|
|
.tag.good {
|
|
color: var(--rhc-color-groen-700);
|
|
}
|
|
.tag.good::before {
|
|
background: var(--rhc-color-groen-500);
|
|
}
|
|
.tag.plain {
|
|
color: var(--rhc-color-grijs-700);
|
|
}
|
|
.tag.plain::before {
|
|
display: none;
|
|
}
|
|
.linked {
|
|
margin: 0 0 1rem;
|
|
}
|
|
.linked .src {
|
|
font-size: 0.72rem;
|
|
color: var(--rhc-color-grijs-700);
|
|
margin: 0.35rem 0 0;
|
|
font-family: monospace;
|
|
}
|
|
`,
|
|
],
|
|
template: `
|
|
<div
|
|
class="card"
|
|
[class.card--good]="variant() === 'good'"
|
|
[class.card--bad]="variant() === 'bad'"
|
|
>
|
|
<p
|
|
class="tag"
|
|
[class.good]="variant() === 'good'"
|
|
[class.bad]="variant() === 'bad'"
|
|
[class.plain]="variant() === 'plain'"
|
|
>
|
|
{{ tag() }}
|
|
</p>
|
|
@if (code(); as c) {
|
|
@if (src(); as s) {
|
|
<figure class="linked">
|
|
<pre class="app-code" [innerHTML]="c"></pre>
|
|
<figcaption class="src">↳ {{ s }}</figcaption>
|
|
</figure>
|
|
} @else {
|
|
<pre class="app-code" [innerHTML]="c"></pre>
|
|
}
|
|
}
|
|
<ng-content />
|
|
</div>
|
|
`,
|
|
})
|
|
export class ConceptCardComponent {
|
|
variant = input<'good' | 'bad' | 'plain'>('plain');
|
|
tag = input.required<string>();
|
|
code = input<string | undefined>();
|
|
src = input<string | undefined>();
|
|
}
|