{% extends "base.html" %} {% block title %}Factcat — Events{% endblock %} {% block extra_head %} {% endblock %} {% block extra_css %} main.workspace { display: block; flex: 1; min-height: 0; } form.workspace-form { display: grid; grid-template-columns: var(--fc-config-col, 22rem) 0 minmax(0, 1fr); height: 100%; min-height: 0; } form.workspace-form.config-collapsed { grid-template-columns: 0 0 minmax(0, 1fr); } form.workspace-form.config-collapsed .filters { display: none; } .filters { background: var(--fc-snow); padding: 1.25rem; border-right: 1px solid var(--fc-line); overflow-y: auto; min-height: 0; grid-column: 1; grid-row: 1; } .col-divider { grid-column: 2; grid-row: 1; } .results { grid-column: 3; grid-row: 1; } .col-divider { cursor: col-resize; position: relative; width: 9px; margin-left: -5px; z-index: 25; display: flex; align-items: center; justify-content: center; touch-action: none; min-height: 0; } .col-divider::before { content: ""; position: absolute; left: 50%; top: 0; bottom: 0; width: 2px; transform: translateX(-50%); background: var(--fc-ochre); opacity: 0; } .col-divider:hover::before, .col-divider.dragging::before { opacity: 1; } form.workspace-form.config-collapsed .col-divider { cursor: default; width: 1.5rem; margin-left: 0; justify-content: flex-start; } form.workspace-form.config-collapsed .col-divider::before { display: none; } form.workspace-form.config-collapsed .col-toggle { width: 1.35rem; height: 3.5rem; border-left: 0; border-radius: 0 0.5rem 0.5rem 0; color: var(--fc-ink); border-color: var(--fc-slate); box-shadow: 0 1px 4px color-mix(in srgb, var(--fc-ink) 22%, transparent); } .col-flyout { position: absolute; left: calc(100% + 0.3rem); top: 50%; transform: translateY(-50%); display: flex; flex-direction: column; gap: 0.2rem; padding: 0.25rem; background: var(--fc-snow); border: 1px solid var(--fc-line); border-radius: 0.4rem; box-shadow: 0 0.35rem 1rem color-mix(in srgb, var(--fc-ink) 18%, transparent); opacity: 0; pointer-events: none; transition: opacity 0.12s ease 0.18s; z-index: 30; } .col-divider:hover .col-flyout, .col-flyout:hover { opacity: 1; pointer-events: auto; } .col-divider.dragging .col-flyout { opacity: 0; pointer-events: none; } .col-flyout button { margin: 0; padding: 0.3rem; width: 1.7rem; height: 1.7rem; display: inline-flex; align-items: center; justify-content: center; background: var(--fc-snow); color: var(--fc-slate); border: 0; border-radius: 0.3rem; cursor: pointer; } .col-flyout button:hover { background: var(--fc-paper); color: var(--fc-ink); } .col-flyout button svg { width: 0.9rem; height: 0.9rem; display: block; } .col-toggle { margin: 0; padding: 0; position: relative; z-index: 1; width: 1.05rem; height: 2.75rem; display: inline-flex; align-items: center; justify-content: center; background: var(--fc-snow); color: var(--fc-slate); border: 1px solid var(--fc-line); border-radius: 0.5rem; cursor: pointer; } .col-toggle:hover { color: var(--fc-ink); border-color: var(--fc-ochre); } .col-toggle svg { width: 0.7rem; height: 0.7rem; display: block; } .col-toggle .chev-right { display: none; } form.config-collapsed .col-toggle .chev-left { display: none; } form.config-collapsed .col-toggle .chev-right { display: block; } .results-toolbar { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0.45rem 0.85rem; padding: 0.2rem 0.35rem 0.45rem; } .results-toolbar .toolbar-field { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; } .results-toolbar .toolbar-field label { display: block; margin: 0; font-size: 0.7rem; font-weight: 600; color: var(--fc-slate); } .results-toolbar select, .results-toolbar input[type="number"], .results-toolbar input[type="date"] { width: auto; margin: 0; font-size: 0.8rem; } .results-toolbar #grain { min-width: 9.75rem; } .results-toolbar #range_choice { min-width: 11.5rem; } .results-toolbar .toolbar-extras { display: flex; flex-wrap: nowrap; align-items: center; gap: 0.55rem 0.75rem; flex: 1 1 auto; min-width: 0; overflow-x: auto; } .toolbar-cluster { display: flex; flex-wrap: nowrap; align-items: center; gap: 0.35rem; min-width: 0; } .toolbar-cluster label, .toolbar-cluster span { display: inline; margin: 0; font-size: 0.75rem; font-weight: 500; color: var(--fc-ink); white-space: nowrap; } .toolbar-cluster input[type="number"] { width: 4.25rem; } .toolbar-cluster input[type="date"] { width: 9.6rem; } .toolbar-cluster select { width: auto; } .toolbar-cluster input[type="checkbox"] { width: auto; margin: 0; } .results-toolbar #custom_kind { min-width: 8.25rem; } .results-toolbar #run-row { display: flex; align-items: center; gap: 0.5rem; margin: 0 0 0 auto; flex-wrap: wrap; } .results-toolbar #run-row button { margin: 0; } .results { display: block; padding: 0 0.5rem 0.5rem; min-height: 0; overflow-y: auto; background: var(--fc-paper); } .results-toolbar { position: sticky; top: 0; z-index: 20; background: var(--fc-paper); margin: 0 -0.5rem 0.5rem; padding: 0.55rem 0.5rem 0.45rem; border-bottom: 1px solid var(--fc-line); } .pane + .pane { margin-top: 0.5rem; } /* The divider's own height provides the chart-table gap; when a closed pane hides it, the next card supplies the same 0.5rem so every inter-card gap is identical in every collapse state. */ .row-divider[hidden] + .pane { margin-top: 0.5rem; } .row-divider { height: 0.5rem; cursor: row-resize; touch-action: none; position: relative; } .row-divider::after { content: ""; position: absolute; left: 50%; top: 50%; width: 2.5rem; height: 3px; border-radius: 2px; transform: translate(-50%, -50%); background: var(--fc-ochre); opacity: 0; } .row-divider:hover::after, .row-divider.dragging::after { opacity: 1; } .row-divider[hidden] { display: none; } .pane-chart:not(.closed) { height: var(--fc-chart-px, 55vh); min-height: 12rem; } .pane-table:not(.closed) .pane-body { max-height: 75vh; } .pane.closed .pane-body { display: none; } .pane-toggle { margin: 0 0 0 0.25rem; padding: 0.15rem; width: 1.5rem; height: 1.5rem; display: inline-flex; align-items: center; justify-content: center; background: none; border: 0; border-radius: 0.25rem; color: var(--fc-slate); cursor: pointer; flex: 0 0 auto; } .pane-toggle:hover { color: var(--fc-ink); background: var(--fc-paper); } .pane-toggle svg { width: 0.85rem; height: 0.85rem; display: block; transition: transform 0.15s ease; } .pane.closed .pane-toggle svg { transform: rotate(-90deg); } .pane-head h2 { cursor: pointer; } .pane { background: var(--fc-snow); border: 1px solid var(--fc-line); display: flex; flex-direction: column; min-height: 0; } .pane h2 { margin: 0; padding: 0.45rem 0.75rem; border-bottom: 1px solid var(--fc-line); font-size: 0.7rem; } .pane-body { flex: 1 1 auto; overflow: auto; padding: 0.75rem; min-height: 0; } .pane-sql .pane-body { padding: 0; } .empty { color: var(--fc-slate); font-size: 0.85rem; margin: 0; } .empty.fail { color: var(--fc-alert); white-space: pre-wrap; } .chart-body { position: relative; } .chart-body canvas { width: 100% !important; height: 100% !important; } .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100%; color: var(--fc-slate); font-size: 0.9rem; text-align: center; margin: 0; } #chart-empty.fail #empty-copy { color: var(--fc-alert); white-space: pre-wrap; } .empty-state[hidden], canvas[hidden], .empty-state img[hidden] { display: none; } .empty-state img { width: 10rem; height: 10rem; object-fit: contain; margin-bottom: 0.75rem; } /* Dots hide via visibility so the centred copy never shifts as they cycle. */ .run-dots span { visibility: hidden; } .run-dots span.on { visibility: visible; } @media (prefers-reduced-motion: reduce) { #waiting-cat { animation: none; } .pane-toggle svg { transition: none; } } #sql { margin: 0; min-height: 100%; box-sizing: border-box; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; font-size: 0.78rem; } code.fc-sql { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; font-size: 0.92em; font-weight: 650; background: var(--fc-ochre-wash); padding: 0.08em 0.28em; border-radius: 0.2rem; } html[data-vocab="sql"] .series-measure, html[data-vocab="sql"] .filter-op, html[data-vocab="sql"] #measure { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; font-size: 0.78rem; } .summary { font-size: 0.75rem; color: var(--fc-slate); margin: 0 0 0.75rem; } .pane-head { display: flex; align-items: center; justify-content: flex-start; gap: 0.5rem; padding-right: 0.5rem; } .pane-head .pane-actions { margin-left: auto; } .pane-head h2 { padding-left: 0; } .pane-head h2 { border-bottom: none; padding: 0.45rem 0.75rem; } .pane-actions { display: flex; align-items: center; gap: 0.35rem; flex-wrap: wrap; } .pane-actions button, .pane-actions select { margin: 0; padding: 0.2rem 0.5rem; font-size: 0.7rem; width: auto; } .pane-actions label { margin: 0; font-weight: 400; display: flex; align-items: center; gap: 0.25rem; } .chart-title { flex: 1; min-width: 8rem; margin: 0; padding: 0.35rem 0.75rem; border: none; border-bottom: 1px solid var(--fc-line); font-size: 0.8rem; font-weight: 600; background: transparent; } #exact-wrap[hidden] { display: none !important; } #run-row { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.75rem; flex-wrap: wrap; } #run-row button { margin-top: 0; } #run-estimate, #table-count { font-size: 0.8rem; color: var(--fc-slate); } #run-estimate-wrap { display: inline-flex; align-items: center; gap: 0.4rem; } #run-estimate-wrap[hidden] { display: none !important; } #run-estimate-bar[hidden] { display: none !important; } #run-estimate-bar { display: inline-block; width: 3.2rem; height: 0.45rem; background: var(--fc-line); border-radius: 2px; overflow: hidden; vertical-align: middle; } #run-estimate-fill { display: block; height: 100%; width: 0; background: var(--fc-ok); } #run-estimate-wrap.warn #run-estimate-fill { background: var(--fc-ochre); } #run-estimate-wrap.hot #run-estimate-fill, #run-estimate-wrap.over-cap #run-estimate-fill { background: var(--fc-alert); } #run-estimate-wrap.over-cap #run-estimate { color: var(--fc-alert); font-weight: 600; } /* Managed tables: cost, reason, next-run cost — in plain words. A child of the (wrapping) toolbar, NOT of #run-row: inside the run row its text width inflated the row's intrinsic size and wrapped the whole row under the date controls, moving Run. As the toolbar's last child with a 100% basis it takes its own line and moves nothing. */ /* One short line after a run that indexed (or failed to): its own toolbar line, right-aligned under the chip and Run, capped so long text wraps from the right and never spans the toolbar. */ #run-note { flex: 1 1 100%; display: flex; justify-content: flex-end; margin: 0; } #run-note > span { max-width: 32rem; text-align: right; font-size: 0.75rem; color: var(--fc-slate); } #run-note.ochre > span { color: var(--fc-ochre-deep); } #run-note[hidden] { display: none !important; } .format-pop { position: relative; font-size: 0.7rem; } .format-pop > summary { cursor: pointer; list-style: none; padding: 0.2rem 0.5rem; border: 1px solid var(--fc-line); border-radius: 0.2rem; } .format-pop > summary::-webkit-details-marker { display: none; } .format-pop[open] > .format-body { position: absolute; right: 0; top: calc(100% + 0.2rem); z-index: 8; background: var(--fc-snow); border: 1px solid var(--fc-line); padding: 0.65rem 0.75rem; min-width: 14rem; box-shadow: 0 4px 14px rgba(28, 25, 23, .08); } .format-body label { display: flex; justify-content: space-between; align-items: center; gap: 0.6rem; margin: 0.4rem 0; font-weight: 400; font-size: 0.7rem; } .format-body select { width: auto; font-size: 0.7rem; padding: 0.15rem 0.3rem; } .icon-btn { display: inline-flex; align-items: center; justify-content: center; width: 1.75rem; height: 1.75rem; padding: 0; margin: 0; background: var(--fc-snow); border: 1px solid var(--fc-line); border-radius: 0.25rem; cursor: pointer; color: var(--fc-slate); } .icon-btn svg { width: 0.95rem; height: 0.95rem; display: block; } .icon-btn:hover { color: var(--fc-ink); border-color: var(--fc-ochre); } .icon-btn.copied { color: var(--fc-ok); border-color: var(--fc-ok); } .format-pop > summary.icon-btn { list-style: none; } .format-pop > summary.icon-btn::-webkit-details-marker { display: none; } .format-pop[hidden] { display: none !important; } /* One geometry for every icon control in a pane head. Outranks both ".pane-actions button" (hits copy buttons) and ".format-pop > summary" (hits download/format summaries), which disagree with .icon-btn. */ .pane-actions button.icon-btn, .pane-actions .format-pop > summary.icon-btn { box-sizing: border-box; width: 1.75rem; height: 1.75rem; padding: 0; border-radius: 0.25rem; } /* The global `details` rule adds margin-top: 0.75rem; inside a centred flex row that rides the popover 6px below its neighbours. */ .pane-actions details { margin: 0; } /* Export chrome is always visible; no content = disabled, never hidden. */ .icon-btn:disabled { color: var(--fc-slate); } .icon-btn:disabled:hover { color: var(--fc-slate); border-color: var(--fc-line); } .format-pop.disabled > summary { opacity: 0.55; cursor: not-allowed; pointer-events: none; } .format-pop[open] > .export-body { position: absolute; right: 0; top: calc(100% + 0.2rem); z-index: 8; background: var(--fc-snow); border: 1px solid var(--fc-line); padding: 0.25rem; min-width: 11rem; box-shadow: 0 4px 14px rgba(28, 25, 23, .08); display: flex; flex-direction: column; gap: 0.1rem; } .export-body button { margin: 0; padding: 0.35rem 0.6rem; width: 100%; background: none; border: 0; border-radius: 0.25rem; color: var(--fc-ink); font-size: 0.72rem; text-align: left; cursor: pointer; } .export-body button:hover { background: var(--fc-paper); } .pane-limit { display: flex; align-items: center; gap: 0.3rem; margin: 0; font-size: 0.7rem; font-weight: 400; color: var(--fc-slate); } .pane-limit input { width: 3.5rem; padding: 0.15rem 0.3rem; font-size: 0.7rem; } #cap-override { display: flex; align-items: center; gap: 0.35rem; font-size: 0.75rem; color: var(--fc-alert); } #cap-override[hidden] { display: none !important; } #result-table { table-layout: fixed; width: 100%; } #result-table th { position: relative; cursor: pointer; user-select: none; min-width: 4rem; border-right: 1px solid var(--fc-line); padding-right: 0.9rem; } #result-table th .sort { font-size: 0.65rem; color: var(--fc-slate); margin-left: 0.25rem; } #result-table th.sorted .sort { color: var(--fc-ink); } #result-table th .grip { position: absolute; right: 0; top: 0; width: 8px; height: 100%; cursor: col-resize; background: linear-gradient(to right, transparent 2px, var(--fc-slate) 2px, var(--fc-slate) 3px, transparent 3px); } #result-table th .grip:hover { background: var(--fc-ochre); } .range-row { display: flex; gap: 0.4rem; align-items: center; margin-top: 0.35rem; } .range-row[hidden], .toolbar-cluster[hidden], #range-include[hidden], #custom-range[hidden], #custom-absolute[hidden], #custom-relative[hidden], #range_n_unit_select[hidden], #rel_unit_select[hidden], #range_n_unit[hidden], #rel_unit_label[hidden] { display: none !important; } .range-row input[type="number"] { width: 4.5rem; } #breakdown-extra[hidden] { display: none !important; } #breakdown-extra .range-row { margin-top: 0.5rem; } .fc-plus { display: inline-flex; align-items: center; justify-content: center; width: 1.65rem; height: 1.65rem; margin: 0; padding: 0; background: var(--fc-snow); color: var(--fc-ochre-deep); border: 1px solid var(--fc-line); border-radius: 999px; cursor: pointer; box-shadow: 0 1px 0 rgba(28, 25, 23, 0.06); } .fc-plus:hover { border-color: var(--fc-ochre); color: var(--fc-ink); background: var(--fc-paper); } .fc-plus svg { width: 0.82rem; height: 0.82rem; display: block; } .fc-plus-row { display: flex; justify-content: center; align-items: center; margin: 0.3rem 0 0.05rem; } .event-group-members .fc-plus-row { margin: 0.2rem 0; } .fc-or { display: flex; align-items: center; gap: 0.55rem; margin: 0.1rem 0.4rem; color: var(--fc-slate); font-size: 0.68rem; font-weight: 600; letter-spacing: 0.08em; user-select: none; } .fc-or::before, .fc-or::after { content: ""; flex: 1; height: 1px; background: var(--fc-line); } #add-breakdown-row[hidden], #add-event-row[hidden], .fc-plus-row[hidden] { display: none !important; } .breakdown-slot-head { margin: 0.65rem 0 0.2rem; } .breakdown-slot-head label { margin: 0; } .breakdown-slot-row, .series-bd-slot:not(:first-child) .series-bd-row { display: flex; align-items: center; gap: 0.45rem; } .breakdown-slot-row select, .series-bd-slot:not(:first-child) .series-bd-row select { flex: 1; min-width: 0; width: auto; margin: 0; } .breakdown-slot-row .linkish, .series-bd-slot:not(:first-child) .series-bd-row .linkish { margin: 0; flex: 0 0 auto; } /* Same card surface as .event-card (border/background/padding values reused verbatim); nested series slots take the .event-group-members .event-card snow background, the shipped card-in-card treatment. */ #breakdown-chart .breakdown-slot, .series-bd-slot { border: 1px solid var(--fc-line); background: var(--fc-paper); padding: 0.5rem 0.55rem 0.4rem; margin: 0 0 0.5rem; min-width: 0; } /* The toggle row above sits flush (0px) otherwise — invisible before the cards drew a border there. Same 0.5rem rhythm as the seams. */ #breakdown-chart .breakdown-slot[data-slot="0"] { margin-top: 0.5rem; } .series-bd-slot { background: var(--fc-snow); margin: 0 0 0.35rem; } .bd-value-controls { margin-top: 0.3rem; } .bd-value-controls[hidden], .bd-fill-wrap[hidden], .bd-fill-expr-wrap[hidden], .bd-value-pair[hidden], #bd-null-nudge[hidden] { display: none !important; } .bd-value-row { display: flex; flex-wrap: wrap; gap: 0.3rem; align-items: flex-start; } .bd-value-pair { display: flex; flex-direction: column; flex: 1 1 9rem; min-width: 7rem; } /* Two-class chains so these beat .series-bd label (0,1,1, later in the file); descendant (not >) so the SQL-expression label inside .bd-fill-expr-wrap is owned here too. */ .bd-value-controls .bd-value-pair label, .bd-value-controls .bd-fill-wrap label, .bd-value-controls .bd-fill-expr-wrap label { margin: 0; font-size: 0.7rem; font-weight: 600; } .bd-value-pair select { width: auto; font-size: 0.75rem; margin: 0; } .bd-fill-wrap { margin-top: 0.3rem; display: flex; flex-wrap: wrap; gap: 0.3rem; align-items: center; } .bd-fill-wrap select, .bd-fill-wrap input { /* min-width 0 lets flex shrink the select below its widest option (long event names) so a 256px column never scrolls sideways. */ width: auto; flex: 1 1 8rem; min-width: 0; font-size: 0.75rem; margin: 0; } /* Column flex so the label and input stretch to the wrap's width; the input opts back out of the row rule above (flex-basis would become HEIGHT in a column context) and takes the full line. */ .bd-fill-expr-wrap { flex: 1 1 100%; display: flex; flex-direction: column; } /* Two-class chain: beats .bd-fill-wrap input on specificity, not source order — the same convention as the label chains above. */ .bd-fill-wrap .bd-fill-expr-wrap input { flex: 0 0 auto; width: 100%; } #bd-null-nudge { margin-top: 0.35rem; } #bd-null-nudge .linkish { margin-left: 0; } .series-bd .remove-breakdown { margin-left: 0.4rem; } #of-wrap[hidden] { display: none !important; } /* Section panels (non-collapsing; heads are static — no pane classes, whose head click listener and margins must not reach this column). */ .cfg-section { border: 1px solid var(--fc-line); background: var(--fc-snow); margin: 0 0 .75rem; } .cfg-section-head { display: flex; align-items: center; justify-content: space-between; gap: .75rem; padding: .4rem .55rem; border-bottom: 1px solid var(--fc-line); font-size: .7rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: var(--fc-ink-soft); } .cfg-section-head .linkish { text-transform: none; letter-spacing: 0; } /* The head must not re-case folded SQL against the user's sql_case preference. */ .cfg-section-head code.fc-sql { text-transform: none; letter-spacing: 0; } .cfg-section-body { padding: .5rem .55rem .45rem; min-width: 0; } .cfg-section-body > .row:first-child { margin-top: 0; } /* Seams: adjacent vertical margins COLLAPSE (the larger wins, they do not add). Exact hidden (the first-run default): section bottom .75rem meets the next section = 12px. Exact shown: .75rem meets #exact-wrap's .75rem on both sides = 12px above and below. The inner .row's global margin-top is zeroed so it cannot collapse through and silently become the seam's owner. */ #exact-wrap { margin: .75rem 0; } #exact-wrap .row { margin-top: 0; } .cfg-section-title { min-width: 0; } .cfg-section-actions { display: flex; align-items: center; gap: 0.75rem; flex: 0 0 auto; } .cfg-section-actions .linkish { margin-left: 0; } .refresh-split { display: inline-flex; align-items: center; } .refresh-split .refresh-more { margin-left: 0.35rem; padding-left: 0.35rem; border-left: 1px solid var(--fc-line); text-decoration: none; line-height: 1; display: inline-flex; align-items: center; } .refresh-split .refresh-more svg { width: 0.7rem; height: 0.7rem; display: block; } .refresh-split .refresh-more[aria-expanded="true"] svg { transform: rotate(180deg); } #find-event[hidden], #find-event-panel[hidden], #event-names-snapshot[hidden], #event-names-status[hidden], #event-names-fallback[hidden] { display: none !important; } #event-names-fallback { margin: 0 0 0.35rem; } #find-event-panel { margin: 0 0 0.45rem auto; padding: 0.15rem 0 0.25rem; width: max-content; max-width: 100%; text-align: left; } .find-event-opt { display: block; width: 100%; margin: 0; padding: 0.25rem 0; border: 0; background: none; color: var(--fc-ochre-deep); font-size: 0.75rem; text-align: left; cursor: pointer; } .find-event-opt:hover { text-decoration: underline; } .find-event-opt .hint { margin: 0.1rem 0 0; } .catalog-status { font-weight: 400; color: var(--fc-slate); font-size: 0.7rem; margin: 0 0 0.35rem; } #event-names-snapshot { margin: 0 0 0.35rem; } .fc-plus-labeled { display: flex; align-items: center; justify-content: center; gap: 0.4rem; width: 100%; margin: 0.35rem 0 0; padding: 0; background: none; border: none; color: var(--fc-ochre-deep); font: inherit; font-size: 0.75rem; font-weight: 500; cursor: pointer; } .fc-plus-labeled .fc-plus { cursor: inherit; box-shadow: none; } .fc-plus-labeled:hover { color: var(--fc-ink); } .fc-plus-labeled:hover .fc-plus { border-color: var(--fc-ochre); color: var(--fc-ink); background: var(--fc-paper); } .event-card, .event-group { border: 1px solid var(--fc-line); background: var(--fc-paper); padding: 0.5rem 0.55rem 0.4rem; margin: 0 0 0.5rem; } .event-group { padding-bottom: 0.5rem; } .event-card-head, .event-group-head { display: flex; align-items: center; gap: 0.35rem; } .event-card-head .event-card-name { flex: 1; margin: 0; min-width: 0; } .event-card-head .series-measure, .event-group-head .series-measure { flex: 0 1 11rem; width: auto; min-width: 7rem; margin: 0; font-size: 0.8rem; } .event-group-title { font-size: 0.75rem; font-weight: 600; color: var(--fc-slate); flex: 0 0 auto; } .event-card-filters { margin: 0.2rem 0 0; } .event-card-filters:has(.filter-line) { margin-left: 0.15rem; padding-left: 0.55rem; border-left: 2px solid var(--fc-line); } .filter-line { display: flex; flex-wrap: wrap; gap: 0.3rem; align-items: center; margin-top: 0.3rem; } .filter-line select, .filter-line input { width: auto; flex: 1 1 5.5rem; font-size: 0.75rem; margin: 0; } .filter-line .filter-json[hidden], .filter-line .filter-expr[hidden], .filter-line .filter-op[hidden], .filter-line .filter-value[hidden], .filter-line .filter-value-to[hidden], .filter-line .filter-value-time[hidden], .filter-line .filter-value-to-time[hidden], .filter-line .filter-case[hidden], .filter-line .filter-pills[hidden], .filter-line .filter-part[hidden], .filter-line .filter-enum[hidden], .filter-line .filter-period[hidden], .filter-line .filter-period-to[hidden] { display: none !important; } .filter-period, .filter-period-to { display: inline-flex; flex-wrap: wrap; gap: 0.3rem; align-items: center; } .filter-week-n, .filter-week-n-to { width: 4.2rem; flex: 0 0 4.2rem; } .filter-quarter-q, .filter-quarter-q-to { width: auto; flex: 0 0 auto; } .filter-part { flex: 0 1 8.5rem; min-width: 7rem; } .filter-pills { display: flex; flex-wrap: wrap; gap: 0.25rem; align-items: center; flex: 1 1 12rem; min-width: 8rem; border: 1px solid var(--fc-line); background: var(--fc-snow); padding: 0.15rem 0.3rem; min-height: 1.75rem; } .filter-pill { display: inline-flex; align-items: center; gap: 0.15rem; max-width: 12rem; padding: 0.05rem 0.15rem 0.05rem 0.4rem; background: var(--fc-paper); border: 1px solid var(--fc-line); font-size: 0.7rem; line-height: 1.2; } .filter-pill > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .filter-pill button { background: none; border: none; padding: 0 0.15rem; margin: 0; cursor: pointer; color: var(--fc-slate); font-size: 0.85rem; line-height: 1; } .filter-pill button:hover { color: var(--fc-ink); } .filter-line .filter-pills-input { flex: 1 1 5rem; min-width: 5rem; width: auto; margin: 0; padding: 0.15rem 0; border: none; background: transparent; font-size: 0.75rem; } .filter-line .filter-pills-input:focus { outline: none; } .filter-case { display: inline-flex; align-items: center; gap: 0.25rem; font-size: 0.7rem; font-weight: 400; margin: 0; flex: 0 0 auto; } .filter-case input { width: auto; margin: 0; flex: none; } .filter-line input[type="date"], .filter-line input[type="time"], .filter-line input[type="number"] { flex: 0 1 8rem; } .event-group-members .event-card { background: var(--fc-snow); margin: 0; } .card-actions { margin-top: 0.35rem; } .add-card-filter { margin: 0.2rem 0 0 0; } .series-bd { margin-top: 0.45rem; } .series-bd[hidden], #breakdown-chart[hidden] { display: none !important; } .series-bd label { font-size: 0.75rem; font-weight: 400; } #chart-measure-template[hidden] { display: none !important; } .series-of { margin: 0.35rem 0 0; } .series-of[hidden] { display: none !important; } .series-of-json[hidden], .series-of-expr[hidden] { display: none !important; } .series-bd-json[hidden], .series-bd-expr[hidden] { display: none !important; } #event_value-wrap[hidden] { display: none !important; } #top_n { width: 4.5rem; } .incomplete-note { font-size: 0.75rem; color: var(--fc-slate); margin: 0.35rem 0 0; } .stale-note { font-size: 0.75rem; color: var(--fc-ochre-deep); margin: 0 0 0.5rem; } .stale-note[hidden] { display: none !important; } #result-table tr.incomplete td { color: var(--fc-slate); font-style: italic; } #custom-snap-hint { margin: 0.35rem 0 0; } .event-card-filters .add-card-filter { margin: 0.2rem 0 0 0; } {% endblock %} {% block body %} Events {% if not mapping_ready %} Map a table on Setup before Events can run. {% endif %} {{ config.table }} · {{ config.entity_label or "User" }} ({{ config.entity }}) · {{ config.event_time }} Event series Refresh event names This list is a snapshot. Refresh rebuilds it. Could not use the event-name snapshot in the write destination — ran a lookback scan instead. Check permissions on fc_event_names. Add series Measure {{ filter_ui.chrome.volume | sql_plain }} {% if filter_ui.vocab == "sql" %}{{ filter_ui.chrome.unique | sql_plain }}{% else %}{{ filter_ui.chrome.unique.replace("{plural}", config.entity_label_plural or "Users") }}{% endif %} {% if filter_ui.vocab == "sql" %}{{ filter_ui.chrome.average_per | sql_plain }}{% else %}{{ filter_ui.chrome.average_per.replace("{singular}", config.entity_label or "User") }}{% endif %} {{ filter_ui.chrome.property_sum | sql_plain }} {{ filter_ui.chrome.property_average | sql_plain }} {{ filter_ui.chrome.property_median | sql_plain }} {% if filter_ui.vocab == "sql" %}{{ filter_ui.chrome.property_distinct | sql_plain }}{% else %}{{ filter_ui.chrome.property_distinct.replace("{singular}", config.entity_label or "User") }}{% endif %} {{ filter_ui.chrome.of | sql_chrome }} Refresh list JSON key Dotted path in this JSON column (plan or user.plan). {{ filter_ui.chrome.sql_expr | sql_chrome }} Fills of=. Sum / Average / Median need a number. Distinct also allows STRING. Unused event types leave this column null. Pick the event that carries it (for example purchase for revenue). Exact unique counts Same bytes scanned; more CPU. Off is HyperLogLog (faster). {{ filter_ui.chrome.bd_section | sql_chrome }} Refresh list {{ filter_ui.chrome.breakdown_each | sql_chrome }} {{ filter_ui.chrome.breakdown | sql_chrome }} JSON key Dotted path in this JSON column (plan or user.plan). {{ filter_ui.chrome.sql_expr | sql_chrome }} Fills the spec expression. Does not replace it with a property store. Top N keeps the biggest groups and folds the rest into (other). (null) stays its own group. Top N Show (other) Use last known at the event Time grain Day Week Month Hour Day of week Hour of day Date range Last days weeks months quarters years {% if config.grain == "week" %}weeks{% elif config.grain == "month" %}months{% else %}days{% endif %} Include today Specific dates Relative From to From to days ago weeks ago months ago quarters ago years ago days ago Override cap Run Auto Line Area Bar Values Auto Integer 1 decimal 2 decimals Compact (1.2k) Scientific Data labels Grid Off Major Major and minor PNG PNG 2× PNG 2× transparent {% if mapping_ready %}No rows yet. The cat is waiting.{% else %}Map a table on Setup before Events can run.{% endif %} Table CSV TSV — pastes into Sheets and Excel Markdown table JSON Run to fill the table. Day {{ filter_ui.chrome.breakdown_th | sql_chrome }} Volume SQL SQL file (.sql) Markdown (.md) SQL updates as you change the form. {% endblock %}
Map a table on Setup before Events can run.
{{ config.table }} · {{ config.entity_label or "User" }} ({{ config.entity }}) · {{ config.event_time }}
This list is a snapshot. Refresh rebuilds it.
Could not use the event-name snapshot in the write destination — ran a lookback scan instead. Check permissions on fc_event_names.
{% if mapping_ready %}No rows yet. The cat is waiting.{% else %}Map a table on Setup before Events can run.{% endif %}
Run to fill the table.
SQL updates as you change the form.