Arc Rider Docs · Markdown

Mini Widgets

Input

Input

AI Defaults
uniqueId
Required, e.g. `"input-name-" + Nr`.
recordId
Required for Ninox binding, use raw `Nr`. Core does not read it — Ninox handler builds the `update` from `ui.data`.
field
Required for Ninox, use `fieldId(Nr, "Feldname")` (alias `fieldId`). Adapter-only.
placeholder
Optional hint text.
type
`"text"` (default), `"number"`, `"date"`, `"time"`, `"textarea"`.
focusAction
Set `{ autoFocus: false }` for tablet compatibility.
Mobile (iPhone/iPad/Android, schmale Viewports)
Text/textarea use keyboard dock above the on-screen keyboard; `type: "date"` / `"time"` / `"number"` use the custom picker (not native / not system keyboard). Reset supports `arcCustomIcon` in the dock (text) or the shared icon slot (date/time/number).
numberFormat
(only `type: "number"`): `{ digits, decimalSeparator, thousandSeparator }` — default DE: `digits: 2`, `","` / `"."`.
numpad
(only `type: "number"`): `{ enabled, size, openOnFocus, allowNegative, maxDecimals, suffix }` — popup numpad (default `enabled: true`). Not the same as the standalone `arcCustomNumPad` widget.
Persistence
Core emits semantic `change` (field value) and `actions` (`inputValue` + `source: "submit" | "reset"`). Ninox adapter writes `type: "update"` with `fieldId` and runs formula `actions` / `reset.actions` (with `##inputValue##`). React: `onChange(value)` / `onActions(inputValue, source)`.
actions
Optional (Ninox formula). Array of action objects, fired on submit (Enter/blur / numpad Fertig) and on reset. Use `##inputValue##` placeholder to inject the current input value. Resolved in the Ninox handler (not passed through React callbacks).
reset.actions
Optional. If set inside `reset: { actions: [...] }`, only these actions fire on reset (not top-level `actions`). Submit still uses `actions`.
liveUpdate
Optional. `true` or `{ debounce: 300, minLength: 0, actions: true }`. Fires updates while typing (debounced). On blur, only fires if value changed since last live update. **Not used by the number numpad** (commit on Fertig / outside / Enter only).

Input

arcCustomInput es un mini-widget versátil y personalizable para mostrar y editar campos de entrada en tu base de datos de Ninox. Sustituye el campo de entrada estándar por una interfaz flexible y visualmente adaptable, que puedes usar directamente en campos de fórmula o código de widgets más complejos — p. ej. en vistas de detalle, dashboards o en combinación con otros widgets como arcCustomTable.

Con arcCustomInput puedes controlar de forma específica entradas de texto, números, valores de fecha u hora, resaltarlos visualmente o dotarlos de placeholders, labels y comportamiento de foco propios. También se admiten entradas de varias líneas (textarea).

🚀 Funciones principales

  • Completamente configurable visualmente (colores, tamaños de fuente, bordes, alineación, etc.)
  • Una línea o varias líneas (texto vs. textarea) — automáticamente según type
  • Control de tabulación entre campos (también entre arcCustomInput y arcCustomSelect)
  • Foco en vivo y lógica de bucle: salto automático a campos (escribir directamente sin hacer clic en el input)
  • Autofoco al iniciar o al hacer clic
  • Vista de solo lectura cuando disabled: true
  • Diseños de placeholder para feedback visual en campos vacíos
  • Botón de reset para restablecer el valor (visible solo si hay un valor presente)
  • Numpad numérico en type: "number" (popup como Date/Time; locale mediante numberFormat)
  • Actions al confirmar (Enter/blur / Numpad-Fertig) y al restablecer — p. ej. para campos de búsqueda, escáneres de código de barras o lógica de disparo

Código de aplicación completo

A continuación verás un código de aplicación de ejemplo para un campo Input

arcCustomInput({
        uniqueId: "Mein-Input-" + Nr,
        recordId: Nr,
        fieldId: "",
        title: text(Zahl),
        value: text(Zahl),
        type: "text",
        embedded: true,
        disabled: false,
        tempStorage: false,
        suffix: "",
        width: "",
        height: "",
        alignX: "left",
        paddingY: "",
        paddingX: "",
        fontColor: "",
        fontSize: "",
        fontWeight: "",
        backgroundColor: "",
        borderWidth: "",
        borderColor: "",
        borderRadius: "",
        numberFormat: {
            digits: 2,
            decimalSeparator: ",",
            thousandSeparator: "."
        },
        numpad: {
            enabled: true,
            size: "default",
            openOnFocus: false,
            allowNegative: false,
            maxDecimals: 2,
            suffix: "€"
        },
        placeholderSettings: {
            value: "",
            fontColor: "",
            backgroundColor: ""
        },
        reset: {
            visible: false,
            value: null,
            icon: "",
            color: "",
            backgroundColor: "",
            borderRadius: ""
        },
        focusAction: {
            width: "",
            showFocusOutline: false,
            outlineWidth: "",
            outlineColor: "",
            loop:",
            nextField: ""
        },
        actions: [
            { type: "update", recordId: Nr, field: fieldId(Nr, "Feld"), value: "##inputValue##" }
        ],
        liveUpdate: false,
        labelSettings: {
            title: "",
            fontSize: "",
            alignX: "",
            gap: ""
        }
    })

Explicación de parámetros individuales

A continuación se detalla qué parámetros puedes usar y qué debes indicar en cada uno.

uniqueId

uniqueId lo asignas individualmente y debe ser único. La razón: si creas varios campos de input con distintos settings en tu página, los estilos no se sobrescriben entre sí.

uniqueId: Nr,

recordId

recordId indica en qué record se debe realizar el cambio.

recordId: Nr,

fieldId

fieldId indica qué campo se debe escribir. Puedes averiguar la Field ID con el mini-widget <a href="/documentation/field-finder"><strong><em>Field Finder</em></strong></a>.

fieldId: "",

value

value indica qué valor se debe mostrar en la interfaz. Es el campo cuya Field ID indicas. Por ejemplo Menge.

Con type: "date": Pasa el valor como milisegundos (timestamp Unix). Ninox almacena así internamente los campos de fecha — puedes pasar el campo directamente, sin text() ni format():

value: Startdatum,

El widget convierte internamente para la visualización y al cambiar vuelve a guardar milisegundos. Fallback: también se acepta un string DD.MM.YYYY.

value: "",

type

type define el tipo de campo de tu input. Usas el type *number* cuando quieres representar un campo numérico de Ninox y *text* cuando quieres representar un campo de texto de Ninox. Indicando *textarea* como type, el input se muestra como campo de varias líneas.

Los types date y time usan en escritorio entrada por segmentos más un custom picker. En móvil (iPhone, iPad, Android o viewport ≤ 768px), pulsar en el campo abre el arcCustom Date-/Time-Picker — no el teclado del sistema. type: "number" usa un popup de numpad (análogo a Date/Time), controlado mediante numberFormat y numpad (ver abajo). En móvil, pulsar en el campo siempre abre el numpad. En escritorio puedes escribir y, opcionalmente, abrir el popup mediante el icono de calculadora (o numpad.openOnFocus: true).

Para text y textarea en móvil: al enfocar, el campo se eleva a un dock de teclado por encima del teclado en pantalla, para que la entrada y el reset permanezcan visibles.

type: "number" // Zahlen-Feld mit Numpad-Popup (Z. B. Betrag, Menge)
type: "text" // Text-Feld (Z. B. für Volltextsuchen)
type: "date" // Datums-Feld mit Datepicker
type: "time" // Zeit-Feld mit Timepicker
type: "textarea" // Mehrzeiliges Input-Feld
type: "" // default: text

numberFormat (solo con type: "number")

Controla la visualización y el parseo inverso de números (locale). Al guardar / ##inputValue## se entrega un número normal (decimal con punto internamente).

numberFormat: {
    digits: 2,                 // Nachkommastellen in der Anzeige (Default: 2). null = so viele wie nötig
    decimalSeparator: ",",     // Default: "," (DE)
    thousandSeparator: "."     // Default: "." (DE). EN-Beispiel: "." / ","
}
PropertyDefaultBeschreibung
digits2Decimales fijos tras el commit (p. ej. 14,50). Mientras escribes en el numpad ves el valor sin formatear (14,5). null = sin relleno.
decimalSeparator","Separador decimal en la UI
thousandSeparator"."Separador de miles en la UI (p. ej. 1.234,56)

Inglés (decimal con punto):

numberFormat: {
    digits: 2,
    decimalSeparator: ".",
    thousandSeparator: ","
}

numpad (solo con type: "number")

Controla el popup del numpad en el input. Esto no es el widget independiente <a href="/documentation/custom-numpad"><code>arcCustomNumPad</code></a> (numpad a pantalla completa/inline con submit propio) — aquí se trata del selector de campo, similar al datepicker.

numpad: {
    enabled: true,           // Default: true. false = nur Tippen (Desktop), kein Popup
    size: "default",         // "default" | "mini"
    openOnFocus: false,      // Desktop: true = Klick ins Feld öffnet Numpad (statt Tippen)
    allowNegative: false,    // ±-Taste
    maxDecimals: 2,          // Max. Nachkommastellen beim Tippen (Default: numberFormat.digits)
    suffix: "€"              // Anzeige im Feld + im Numpad (optional; Fallback: top-level suffix)
}
PropertyDefaultBeschreibung
enabledtrueNumpad activado/desactivado
size"default""mini" = panel más compacto
openOnFocusfalseEscritorio: clic en el campo abre el numpad. Móvil: siempre numpad al tocar
allowNegativefalseAlternar signo
maxDecimalsnumberFormat.digitsLimita los decimales durante la entrada; 0 = sin coma
suffix"" / top-level suffixp. ej. €, Stk — aparece en la visualización del campo (14,50 €) y en el display del numpad

Comportamiento

  • El valor solo se escribe al pulsar Fertig, hacer clic fuera o pulsar Enter (sin liveUpdate a través del numpad).
  • Escape cierra sin guardar.
  • El campo numérico permanece visible; el numpad se acopla al campo (preferentemente encima).
  • Reset: Con reset.visible: true y un valor presente aparece solo la X (como en Date) — no calculadora y X a la vez. Vacío / sin reset → el icono de calculadora abre el numpad.

Ejemplo: importe con mini-numpad

arcCustomInput({
    uniqueId: "betrag-" + Nr,
    recordId: Nr,
    fieldId: fieldId(Nr, "Betrag"),
    value: Betrag,
    type: "number",
    alignX: "right",
    numberFormat: {
        digits: 2,
        decimalSeparator: ",",
        thousandSeparator: "."
    },
    numpad: {
        enabled: true,
        size: "mini",
        openOnFocus: true,
        suffix: "€"
    },
    reset: { visible: true },
    placeholderSettings: { value: "0,00" },
    labelSettings: { title: "Betrag" }
})

Ejemplo: número entero (cantidad)

arcCustomInput({
    uniqueId: "menge-" + Nr,
    recordId: Nr,
    fieldId: fieldId(Nr, "Menge"),
    value: Menge,
    type: "number",
    alignX: "right",
    numberFormat: { digits: 0, decimalSeparator: ",", thousandSeparator: "." },
    numpad: { enabled: true, maxDecimals: 0, suffix: "Stk" },
    placeholderSettings: { value: "0" }
})

timeSettings (solo con type: "time")

Con type: "time", timeSettings controla el Custom Time Picker (análogo al date picker):

timeSettings: {
    format: "24h",      // "24h" (default) oder "12h" (AM/PM links)
    minuteStep: 5,      // Minuten-Schritte: 5 (00,05,10...) oder 1
    showNow: true,      // "Jetzt"-Button (default: true)
    hours: [            // Optional: Overrides für einzelne Stunden
        { value: 10, label: "10", backgroundColor: "#e8f4fd", fontColor: "#1a73e8" }
    ],
    minutes: [          // Optional: Overrides für einzelne Minuten
        { value: 30, label: "½", backgroundColor: "#f0f0f0" }
    ],
    labels: [{ hours: "Stunden", minutes: "Minuten" }]  // Optional: Übersetzung
}
  • format: "24h" (default) muestra 00–23, "12h" muestra 01–12 con selector AM/PM a la izquierda.
  • minuteStep: 5 para pasos de 5 (00, 05, 10 … 55), 1 para resolución completa de minutos.
  • showNow: true muestra el botón «Ahora». Hacer clic en un minuto cierra el picker.
  • hours / minutes: arrays con overrides. Solo las entradas indicadas sobrescriben los defaults (value, label, backgroundColor, fontColor).
  • labels: array con objeto para etiquetas de sección traducibles. Ejemplo: labels: [{ hours: "Stunden", minutes: "Minuten" }]. Se usa el primer elemento. Las keys faltantes usan los defaults ("HOURS", "MINUTES").

embedded

embedded lo usas cuando no insertas el botón dentro de otro widget. Por defecto está establecido en true.

embedded: false, // ermöglicht den Standalone Einsatz des Widgets (nicht eingebettet in anderen Widgets)
embedded: true,  // Fallback

tempStorage

Con tempStorage: true/false se puede adoptar el valor introducido sin que haya un campo de Ninox detrás. Este caso de uso solo tiene sentido cuando los datos se introducen solo temporalmente y no deben guardarse directamente en Ninox. Combinado con actions puedes construir p. ej. un campo de búsqueda: el valor no se guarda, pero al confirmar se disparan las actions (p. ej. un update sobre un campo helper o una URL con ##inputValue##).

tempStorage: "true", // der eingegebene Wert wird nur temporär gespeichert
tempStorage: "false", // default

width

width indica el ancho de tu campo de input.

width: "50px", // Pixel Werte in "
width: "100%", // Prozent Werte in "

height

height indica el alto de tu campo de input.

height: "50px", // Pixel-Werte in "

color

color indica el color de fuente del texto de entrada.

color: "#000000", // HEX Wert in "

background

background indica el color de fondo de tu campo de input.

💡 Consejo: Si insertas el input en otro widget, como <a href="https://www.arc-rider.de/documentation/custom-layout">Layout</a>, <a href="/documentation/custom-table">Table</a>, <a href="/documentation/cards">Card</a>, etc., puedes poner el color de fondo en background: "transparent". Así el input se integra sin costuras y se adapta visualmente de forma perfecta al fondo.

background: "#EEEEEE", // HEX Wert in "
backgrond: "transparent" // keine Hintergrundfarbe

fontSize

fontSize indica el tamaño de fuente de tus textos de input.

fontSize: "12px" // Pixel Werte in "

fontWeight

fontWeight indica el grosor de tu fuente.

fontWeight: "800", // Zahl in " Default sind 400

alignX

alignX indica la alineación de tu texto.

alignX: "left", // left orientiert deinen Text linksbündig innerhalb des Input-Feldes
alignX: "center", // center orientiert deinen Text zentriert innerhalb des Input-Feldes
alignX: "right", // right orientiert deinen Text rechtsbündig innerhalb des Input-Feldes

paddingY

paddingY indica la distancia entre el contenido (aquí: contenido del input) y el borde, arriba y abajo respectivamente. Este parámetro tiene sentido sobre todo con type="textarea".

paddingY: "20px", // Pixel-Werte
paddingY: "", // default: 2px

paddingX

paddingX indica la distancia entre el contenido (aquí: contenido del input) y el borde, a izquierda y derecha respectivamente.

paddingX: "20px", // Pixel-Werte
paddingX: "", // default: 8px

suffix

suffix define un sufijo en la visualización (no forma parte del valor guardado). Con type: "number" puedes establecer lo mismo también bajo numpad.suffix (numpad.suffix tiene prioridad).

suffix: "€", // Anzeige z. B. 1.234,56 € — gespeichert wird nur die Zahl

Bloque Placeholder Settings

placeholderSettings permite establecer ajustes para el placeholder del campo de input. Entre otras, existen las siguientes opciones: value: indica qué se debe mostrar como placeholder antes de escribir. Por ejemplo: "0" o "Suche", o también fórmulas y condiciones de Ninox (como p. ej. if cnt(Ergebnisse)!= null then "Suche" else " end).:

placeholderSettings: {
            value: "Suche", // Wert der ausgegeben werden soll
            fontColor: "#000", // Schriftfarbe
            backgroundColor: "" // Hintergrundfarbe
        },

Bloque reset

El bloque reset controla un botón de restablecer a la derecha del campo de entrada. El botón solo se muestra cuando hay un valor en el campo — con el campo vacío desaparece automáticamente.

  • visible – Si es true, se muestra el botón de reset (siempre que haya un valor presente).
  • value – Opcional. El valor que se establece al hacer clic. Default: null. Con type: "date", pásalo como milisegundos (p. ej. number(date(calYear, calMonth, 1)) para el 1 del mes, today() para hoy). Este valor se inserta 1:1 como ##inputValue## en las actions.
  • icon – Opcional. Icono propio como string HTML (p. ej. vía arcCustomIcon). Sin indicarlo, se usa un icono X estándar.
  • color – Opcional. Color del icono (para el icono por defecto) o del texto (para más adelante).
  • backgroundColor – Opcional. Color de fondo del botón de reset.
  • borderRadius – Opcional. Redondeo de las esquinas del botón de reset.
  • actions – Opcional. Array de actions propio solo para el reset. Si se establece, al hacer reset solo se disparan estas actions en lugar de las actions de nivel superior. ##inputValue## se sustituye por reset.value.
reset: {
    visible: true,
    value: null,
    icon: arcCustomIcon({
        name: "x",
        color: "#666666",
        iconSize: "14px"
    }),
    color: "#555555",
    backgroundColor: "#f0f0f0",
    borderRadius: "4px",
    actions: []
}

💡 Notas

  • El botón de reset solo aparece cuando el campo contiene un valor.
  • Con type: "date" / "time" / "number" (con numpad) se usa un icono a la derecha: icono de picker si está vacío, X si hay valor + reset.visible: true (nunca dos iconos superpuestos).
  • Con reset.visible: true se usa reset.value al hacer clic (p. ej. saltar al 1 del mes en lugar de vaciar).
  • Con disabled: true no se muestra el botón de reset.
  • Sin icon se usa un icono X estándar (Phosphor).

actions

Con actions puedes definir un array de acciones que se ejecutan al confirmar (Enter o blur al cambiar el valor) y al restablecer. Esto corresponde al mismo patrón que en arcCustomButton.

Momentos de disparo

  • Submit: Cuando el usuario pulsa Enter o abandona el campo y el valor ha cambiado.
  • Reset: Cuando el usuario hace clic en el botón de reset (después del update en la BD). Placeholder ##inputValue## En todas las propiedades de string de los objetos de action puedes usar ##inputValue##. El placeholder se sustituye en tiempo de ejecución por el valor de input actual (formateado) — al confirmar por el valor introducido, al restablecer por reset.value (o vacío, si reset.value no está establecido). Con type: "date" el formato es siempre un string de milisegundos (submit y reset).

Tipos de action admitidos

Como en arcCustomButton: update, popup, openRecord, delete, openUrl, customJS, create, etc. Para openUrl y customJS puedes usar action en lugar de actionValue.

Ejemplos

Campo de búsqueda (con tempStorage: true — el valor no se guarda, la action se dispara igualmente):

actions: [{ type: "update", recordId: Nr, field: fieldId(Nr, "helper_search"), value: "##inputValue##" }]

Escáner de código de barras (actualizar campo y abrir popup):

actions: [
    { type: "update", recordId: Nr, field: fieldId(Nr, "Barcode"), value: "##inputValue##" },
    { type: "popup", recordId: Nr }
]

Abrir URL con término de búsqueda:

actions: [{ type: "openUrl", action: "https://example.com/search?q=##inputValue##" }]

reset.actions

Opcional. Si dentro del bloque reset se define un array actions propio, al restablecer solo se disparan estas actions (no las actions de nivel superior). Al confirmar se siguen ejecutando las actions de nivel superior. Sin reset.actions, submit y reset se comportan como antes — ambos usan actions.

Ejemplo: campo de fecha de calendario con reset al 1 del mes

arcCustomInput({
    uniqueId: "cal-date-" + Nr,
    recordId: Nr,
    fieldId: fieldId(Nr, "cal_date"),
    type: "date",
    value: calDate,
    reset: {
        visible: true,
        value: number(date(calYear, calMonth, 1)),
        actions: [{ type: "update", recordId: Nr, field: stateField, value: stateReset }]
    },
    actions: [{ type: "update", recordId: Nr, field: fieldId(Nr, "cal_date"), value: "##inputValue##" }]
})

liveUpdate

Con liveUpdate, los valores se envían a Ninox mientras se escribe — no solo al salir del campo (blur). Ideal para campos de búsqueda en tiempo real, filtros en vivo o escenarios de preview.

Variante simple (boolean – debounce de 300 ms, las actions se disparan también):

liveUpdate: true

Variante de objeto (control total):

liveUpdate: {
    debounce: 300,     // ms – Verzögerung zwischen Tastendrücken (Default: 300)
    minLength: 3,      // Mindestzeichen bevor gefeuert wird (Default: 0)
    actions: false     // Sollen actions[] auch live feuern? (Default: true)
}

Opciones

PropertyTypDefaultBeschreibung
debouncenumber300Tiempo de espera en ms tras la última pulsación
minLengthnumber0Longitud mínima del valor de entrada antes de que se dispare un update
actionsbooleantrueSi el array actions[] también debe ejecutarse en cada live-update

Comportamiento

  • En el blur solo se dispara si el valor ha cambiado desde el último live-update (sin doble update).
  • Se respeta tempStorage: true — con live-update no se hace entonces ningún update en la BD, pero las actions se disparan igualmente (si actions: true).
  • El placeholder ##inputValue## en las actions también funciona con live-updates.

Ejemplo: campo de búsqueda en vivo

arcCustomInput({
    uniqueId: "search-" + Nr,
    recordId: Nr,
    fieldId: fieldId(Nr, "helper_search"),
    value: text(helper_search),
    type: "text",
    embedded: true,
    placeholderSettings: { value: "Suche..." },
    liveUpdate: { debounce: 400, minLength: 2 },
    actions: [{ type: "update", recordId: Nr, field: fieldId(Nr, "helper_search"), value: "##inputValue##" }]
})

Bloque Focus Action

En focusAction defines cómo se ve tu campo de input en estado activo. Es decir, al hacer clic dentro del campo de input. Aquí se puede ajustar el ancho del campo de input mientras se hace clic dentro. Además, se pueden hacer ajustes para el outline.

loop en el bloque Focus Action

La función loop hace que el cursor salte automáticamente al siguiente campo de entrada, o incluso al mismo.

Caso de uso: campo de búsqueda con foco automático

Un ejemplo práctico es un campo de búsqueda al que el cursor vuelve automáticamente tras una acción.

Por ejemplo:

  • Un usuario introduce un término de búsqueda.
  • Elige un producto de la lista de resultados y lo añade al carrito.
  • En lugar de tener que hacer clic de nuevo en el campo de búsqueda, el cursor salta directamente de vuelta a él.

Ideal para flujos de código de barras

Esta función es especialmente útil para flujos de código de barras, en los que el campo de entrada debe permanecer escribible después de cada confirmación. Si varios campos están equipados con la opción loop, el foco salta automáticamente al último campo.

Esta función ahorra clics y hace más eficientes las entradas repetidas.

Bloque Label Settings

labelSettings permite colocar un label sobre el campo de input. Existen las siguientes opciones:

labelSettings: {
    title: "Mein Label", // Die Beschriftung des Labels (Text oder Mini-Widget)
    fontSize: "", // Die Größe des Labels (Default ist 11px)
    fontColor: "#EEEEEE", // Schriftfarbe des Labels
    alignX: "",  // Die horizontale Anordnung ist über left oder right steuerbar
    gap: "" // Wert in Pixel (3px) bestimmt den Abstand zum Input-Feld
}

Valores por defecto como fallback

💡 Nota: Si no indicas valores para los parámetros, en la mayoría de los casos hay un valor de fallback definido en el sistema. En algunos lugares, como campos de valor (por ejemplo valores numéricos reales), esto no tiene sentido. En parámetros de estilo, como colores o tamaños, sí. Aquí ves el código sin valores introducidos y, en los comentarios detrás, los valores estándar:

arcCustomInput({
        uniqueId: Nr, // Kein Fallback
        recordId: Nr, // Kein Fallback
        fieldId: "", // Kein Fallback
        value: "", // Kein Fallback
        placeholder: "", // Kein Fallback
        width: "", // Fallback: 100%
        color: "", // Fallback: #000000
        background: "", // Fallback: #FFFFFF
        fontSize: "" // Fallback: 13px
    })

---

Idea de función: Nice Select (inspiración para Input/Select)

💡 Ampliación planeada: <a href="https://nerdy.dev/nice-select#try-it">Nice Select</a> — estilización de select nativo moderna con appearance: base-select, animaciones spring, esquinas squircle, light/dark. Patrones transferibles a foco de input, theming, RTL. <a href="https://codepen.io/editor/argyleink/pen/019c1f28-bbc2-7bac-ad4a-a7e41d3730f1">CodePen</a>.