Card-Frame als rechteckiges Geschwister-SDC zu bubble-card-frame
card-frame implementiert, parallel zu bubble-card-frame (kein Refactor). Vier Border-Varianten × vier Hintergrund-Varianten × optionaler Button-Notch, gerendert über ein einzelnes SVG-Path (Stroke + Fill konditional), Image-Hintergrund über HTML-<img> mit clip-path referenziert auf SVG-<clipPath>.
Kontext
Figma File CHHDfbpX8Ys2LBiGs5HVK0 definiert das Component-Set Card/Desktop/Frame (Node-IDs 61176:47682 ff.) als rechteckige Card mit vier Border-Varianten und unabhängig davon vier Hintergrund-Varianten. Anders als die SOLCOM-Profile-Card (Figma 4.3, profile-2-2, gemodellt von ADR-0005):
- Geometrie: Rechteck mit
border-radius 20 px(statt 36 px der Profile-Card), variable Card-Width und Card-Height, kein festes Aspect-Ratio. - Border-Achse mit vier Werten:
none— kein Stroke, Card-Body kommt ohne Outline aus.default— geschlossener Rounded-Rect-Stroke.speaker-top— Stroke mit dreieckiger Spitze an der oberen linken Ecke (Sprechblasen-Tail nach oben). Top-Left-Corner wird durch das Dreieck ersetzt, Left-Edge fährt straight durch den Peak hindurch.speaker-bottom— analog mit Tail an der unteren linken Ecke.
- Background-Achse (orthogonal zur Border-Achse, jede Kombination valid):
none— transparenter Card-Body, Page-Background sichtbar.glass— Off-White-Overlay#FDFBF433(Figma 61424:22034 Color-Section, „Glass-on Dark" 20 % Alpha).farbe— Glass-Base PLUS diagonalem Brand-Gradient bei 20 % Opazität (Lime → Teal → Dark Blue, Figma 61447:23485 „Glass-on Light").bild— Hintergrundbild mit Dark-Blue-Gradient-Overlay 180 deg (75 % → 0, Figma 61424:22041 „Foto"). Media-Library-Picker via SDC-Propbackground_imagemit$refaufcanvas.module/image.
- Button-Notch: optionaler Slot
button. Wenn belegt, schneidet der Stroke unten-rechts eine Pille-große Öffnung aus, in der die CTA sitzt; Notch-Width folgt der echten Pille-Width zur Layout-Time. - Mobile (
≤ 768 px): Button rückt in den normalen Flow unter den Content (Flex-Column-Gap 32 px), Notch wird nicht gerendert.
Die bubble-card-frame-SDC aus ADR-0005 deckt nur die Profile-Form ab (profile-2-2, 544×581-Geometrie mit fixem 36-px-Outer-Radius und fest 60-px-Step-Down-Notch). Sie ist nicht parametrisierbar auf die Frame-Topologie, weil:
- der Outer-Corner-Radius hardcoded ist (Path-Builder rechnet mit BR=36),
- die Geschwister-SDCs
card-bubble-profile/card-bubble-profile-no-buttonihren Padding-Layout fest auf die Profile-Variante ausrichten (padding-bottom: 3.625remfür Notch-Reserve), - die Speaker-Tail-Geometrie (oben/unten links, mit straight Left-Edge durch den Peak) keine Parallele in der Profile-Card hat.
Entscheidung
Neue SDC web/themes/custom/byte_theme/components/card-frame/ mit eigener JS-Library byte_theme/card-frame-notch, parallel zu bubble-card-frame und bubble-card-notch. Kein Refactor.
SDC-Topologie
- Props:
variant(required, list_string):none | default | speaker-top | speaker-bottom, defaultdefault.background(required, list_string):none | glass | farbe | bild, defaultnone.background_image(optional,$ref: canvas.module/image): nur wennbackground = bild.
required: true, damit Canvas das „— None —"-Field im Editor-Dropdown nicht anbietet — leerer String verletzt die Enum-Validation und crasht den Render. Twig coerced zusätzlich viavariant|default('default') ?: 'default'jeden Fall, in dem doch ein""durchkommt (z. B. legacy-gespeicherte Instances, API-Push). - Slots:
content— Hauptinhalt der Card.button— optionaler CTA-Slot. Wenn leer, kein Notch.
{{ content }}/{{ button }}gerendert (NICHT{% block %}{% endblock %}), weil der Canvas-PrintNode-Visitor (CanvasPropVisitor.php) nurPrintNode-Ausdrücke mit Slot-Boundary-Comments wrappt —{% block %}ist für Canvas unsichtbar und der Editor zeigt keine Drop-Zone. - Der Button-Slot-Wrapper bleibt IMMER im DOM (auch leer), mit
min-width: 8rem; min-height: 2.75rem, damit der Canvas-Editor eine sichtbare Bounding-Box für die Drop-Zone hat. Frontend-Render mit truly-:empty-Wrapper bekommt via CSSdisplay: none.
SVG-Path-Geometrie
Ein einzelnes SVG mit ViewBox 0 0 W H (1:1 zur Article-Box, kein Stretch für Peak/Tail) und preserveAspectRatio="none". ViewBox wird im JS auf die echte Card-Width/Height aktualisiert, Path-d zur Layout-Time per buildPath() neu gebaut (analog ADR-0005). Speaker-Tails bei y = -32 bzw. y = h + 32 rendern via CSS overflow: visible außerhalb der SVG-Display-Box.
| Variante | Path-Konstruktion |
|---|---|
default + button | Rounded-Rect + Notch unten-rechts. Notch-Left-Seite = zwei tangential verbundene Quarter-Arcs (K=0.5523 Cubic-Bezier-Approximation) → „Rundung-an-Rundung"-Optik wie in der Figma-Vorlage. |
speaker-top | Path startet bei M tx 0 (NICHT M br 0); Top-Left-Corner entfällt. Left-Edge fährt am Pfad-Ende straight durch (0, 0) bis Peak (0, -th); Z schließt die Hypotenuse zurück zur M-Position. |
speaker-bottom | Bottom-Edge endet bei (tx, h), Diagonale zum Peak (0, h+th), dann straight up. Bottom-Left-Corner entfällt. |
none | Stroke transparent. Path-Geometrie wird trotzdem berechnet — wird vom Hintergrund-Fill als Card-Shape-Container genutzt. |
Background-Rendering
glass: SVG-Path-Fillrgba(253, 251, 244, 0.2)— Figma-Hex#FDFBF433zerlegt in RGB + 20-%-Alpha. Single-Path-Setup.farbe: Zwei<path>-Elemente mit identischemd. Erstes Path = Glass-Base-Fillrgba(253, 251, 244, 0.2), zweites Path =<linearGradient>-Stops Lime/Teal/Blue je 20 % Opazität (diagonal x1=0,y1=0 → x2=1,y2=1). Base-Fill ist nötig, weil 20-%-Blue über dunklem SOLCOM-Page-Background fast unsichtbar wäre — Figma rendert das Frame auf neutralem Mittelgrau.bild: HTML<img>-Element mitobject-fit: coverin einem separaten.card-frame__bg-Wrapper, gestapelt unter der SVG-Outline. Wrapper bekommtclip-path: url(#card-frame-clip-<id>);<clipPath>liegt in den SVG-<defs>mit demselbendwie der Outline-Path. Damit läuft das Bild pixelgenau um den Button-Notch herum, anstatt durchzulaufen. Dark-Blue-Gradient-Overlay (Figma 61424:22041 Color-Section:180deg, rgba(0,31,70,0.75) 53.04%, rgba(0,31,70,0) 106.33%) als Sibling-Div im Wrapper. SVG-<pattern>-Ansatz wurde rejected, weilpreserveAspectRatio="none"der äußeren SVG das Pattern-Image vertikal staucht.
JS-Behavior — card-frame-notch.js
Drupal.behaviors.solcomCardFrameNotch.attach registriert pro .card-frame-Instanz zwei ResizeObserver (Card + Pille) plus einen window.resize-Listener. Bei jedem Fire: card.getBoundingClientRect() liefert echte Card-Width/Height (Border-Box, padding-inklusive), buildPath() erzeugt neuen Path-d, svg.querySelectorAll('path') updated alle Path-Elemente (Single-Path-Fall none/glass, Double-Path-Fall farbe, Clip-Path-Fall bild via <clipPath>><path>) mit dem identischen d-String. Geometrie-Konstanten (BR=20, TX=32, TH=32, SD=60, K=0.5523, PILLE_*-Paddings) hardcoded.
Canvas-Component-Config-Wiring
Drupal-Canvas auto-discovered die SDC und schreibt config/sync/canvas.component.sdc.byte_theme.card-frame.yml aus dem Schema. variant und background werden als list_string-Fields gemapped, background_image als entity_reference auf Media-Bundles image + svg_image mit dem media_library_widget. Schema-Hash-Änderung (z. B. neue Prop, Required-Flag) treibt einen neuen active_version-Hash — bestehende Placements bleiben an der alten Version gepinnt, bis sie explizit gebumpt werden.
Rejected alternatives
-
Refactor
bubble-card-framezu einer parametrisierten Shape-SDC. Verworfen: Outer-Radius BR=36 ist im Path-Builder, im JS-Behavior und in der CSS-Padding-Reserve (padding-bottom: 3.625rem) der Profile-Card hardcoded; Container-Topologie und Slot-Schema sind verschieden (Profile-Card hat keinen Background-Prop, kein Speaker-Tail, keine Variant-Achse außerprofile-2-2vsprofile-2-2-no-cta). Ein parametrisierender Refactor würde drei eigenständige Komponenten in einen einzelnen Schema-Knoten verschmelzen, mit dem Risiko, bestehende Canvas-Placements via Versions-Hash-Bump zu invalidieren. Parallel-SDC ist günstiger und reversibler. -
SVG-
<pattern>mit eingebettetem<image>für diebild-Variante. Verworfen:preserveAspectRatio="none"der äußeren SVG (notwendig damit der Path 1:1 zur echten Card-Box scaled) staucht das Pattern-Image vertikal — Personen auf Foto-Hintergründen wirken verzerrt. HTML-<img>mitobject-fit: coverist der native Cover-Modus, völlig unabhängig vom SVG-Aspect-Ratio. - Container-Query-getriebener Border-Stretch ohne JS. Verworfen aus den gleichen Gründen wie in ADR-0005: Path-Builder braucht absolute Pixel-Maße für die korrekte Notch-Position, kein Stretch-Mode bringt die nötige Geometrie-Granularität.
- Speaker-Tail als rechteckiges Anhängsel (Auflage statt Ecken-Ersatz). Erste Implementierung. Verworfen nach Vergleich mit dem Figma-Frame: dort ersetzt die Spitze die Ecke, sie liegt nicht zusätzlich an. Die straight-Left-Edge durch den Peak ist ein Brand-Signal („Sprechblase, deren Tail aus dem Body extrudiert"), nicht „Box mit angeklebtem Dreieck".
-
Empty-String
""als gültiger Enum-Wert mitmeta:enum-Label „— (Default)". Erwogen für Editor-UX (Author kann „Nichts ausgewählt" wählen, Twig coerced). Verworfen, weil das Hinzufügen von""die Canvas-StorablePropShape-Inference bricht (list_string-Field kann keinen Empty-String als Stored-Value haben — Assertion-Crash mit Editor-500). Stattdessen:required: trueentfernt das „— None —"-Option im Dropdown, Twig-?:-Coercion bleibt als Safety-Net für Legacy/API-Inputs.
Consequences
- Card-Familie wächst auf fünf Members:
card(ADR-0009),card-bubble-profile+card-bubble-profile-no-button(ADR-0005),card-meta-list(ADR-0013),card-feature(ADR-0017) und nuncard-frame. Wiki-Entitycard-familyaufgefrischt (siehe LOG.html). - Duplizierte Geometrie-Konstanten zwischen
bubble-card-notch.js,card-feature-notch.jsundcard-frame-notch.js(K-Approximation, PILLE_RIGHT_PAD, PILLE_LEFT_OVERHANG, S-Curve-Subpath-Builder). Bewusste Akzeptanz: eine gemeinsamejs/lib/notch-geometry.js-Schicht wäre eine ADR-pflichtige Cross-SDC-Refactor-Entscheidung und ist Out-of-Scope dieses Card-Frame-Builds. Ein zukünftiges ADR kann das aufgreifen, wenn ein vierter SDC den gleichen Notch braucht. - CSS-Duplikat zwischen
.bubble-card*und.card-frame*insrc/main.css(Container-Type, Position, Isolation, z-Index-Stack, Notch-Padding-Reserve). Gleiche Begründung wie oben: lift auf eine geteilte Base-Class wäre ein eigener Refactor mit Risiko, die etablierten Bubble-Card-Selektoren zu brechen. - Canvas-Editor-Verhalten: beim Bumpen der
card-frame.component.yml(z. B. neue Prop) wird dieactive_versionrehashed. Bestehende Placements bleiben an der alten Version, bis sie explizit migriert werden. Migration-Pattern siehe ADR-0002; für routinemäßige Drift-Heilung von Autosave-Drafts undprop_field_definitionssiehe ADR-0039. - Reversibel: Ein kompletter Rückbau ist via
rm -rf web/themes/custom/byte_theme/components/card-frame/ web/themes/custom/byte_theme/js/card-frame-notch.jsplus Entfernen descard-frame-notch-Eintrags ausbyte_theme.libraries.yml+ Löschen der Canvas-Config-File möglich. Bestehende Placements crashen dann mit broken component — Canvas zeigt ein placeholder, der Page-Tree wird nicht zerstört.