@import "tailwindcss" source(none); @import "phoenix-colocated/tarakan/colocated.css"; @source "../css"; @source "../js"; @source "../../lib/tarakan_web"; @source "../../_build/dev/phoenix-colocated/tarakan/*/"; @plugin "../vendor/heroicons"; @custom-variant phx-click-loading (.phx-click-loading&, .phx-click-loading &); @custom-variant phx-submit-loading (.phx-submit-loading&, .phx-submit-loading &); @custom-variant phx-change-loading (.phx-change-loading&, .phx-change-loading &); [data-phx-session], [data-phx-teleported-src] { display: contents; } /* Tailwind's preflight leaves native buttons on the default arrow cursor. Make the interaction contract consistent without repeating cursor utilities on every component and LiveView action. */ @layer base { html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; } body { overflow-x: clip; } /* Prefer larger tap targets on coarse pointers without changing desktop. */ @media (pointer: coarse) { button:not(:disabled), a, summary, [role="button"] { touch-action: manipulation; } } button:not(:disabled), input:is([type="button"], [type="submit"], [type="reset"]):not(:disabled) { cursor: pointer; } button:disabled, input:is([type="button"], [type="submit"], [type="reset"]):disabled { cursor: not-allowed; } } /* Chakra Petch (400/500/700), latin subset, vendored in priv/static/fonts */ @font-face { font-family: "Chakra Petch"; font-style: normal; font-weight: 400; font-display: swap; src: url("/fonts/chakra-petch-latin-400.woff2") format("woff2"); } @font-face { font-family: "Chakra Petch"; font-style: normal; font-weight: 500; font-display: swap; src: url("/fonts/chakra-petch-latin-500.woff2") format("woff2"); } @font-face { font-family: "Chakra Petch"; font-style: normal; font-weight: 700; font-display: swap; src: url("/fonts/chakra-petch-latin-700.woff2") format("woff2"); } /* Tarakan color tokens. Dark (flagship): pure void black, hard white type, signal red for findings and primary actions, amber for secondary callouts. Borders stay cold grey so red only hits where it means something (findings, CTAs, live pulse). Light: clinical paper, black ink, the same pure red under control. */ :root { --ground: #f4f4f4; --panel: #ebebeb; --ink: #0a0a0a; --ink-muted: #444444; /* --ink-faint carries real telemetry (SHAs, timestamps, run counts) at 10-11px, so it has to clear WCAG AA against BOTH --ground and --panel, not just --ground. #666 = 5.2:1 on ground, 4.8:1 on panel. The old #777 was 4.1:1 / 3.8:1. */ --ink-faint: #666666; --rule: #cccccc; --strong: #111111; --signal: #c40018; --quote: #9a6b00; --btn: #c40018; --btn-fg: #ffffff; /* The well is the surface live values sit on. Light keeps it visually flush with the ground - a dark slab reads as a defect on paper - but it must be an opaque paint, not transparent: wire-badge hollows fill with --well and would otherwise expose the badge's own stroke color. Dark gets the true phosphor treatment: recessed slab, scanlines, glow. */ --well: #f4f4f4; --well-pad: 0; --phosphor: #c40018; --glow: none; --glow-soft: none; --scan: none; --live-edge: var(--signal); } /* Dark tokens apply via OS preference, and via the manual toggle (data-theme). Keep the two dark blocks identical. */ @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --ground: #000000; --panel: #0a0a0a; --ink: #f2f2f2; --ink-muted: #9a9a9a; --ink-faint: #7d7d7d; --rule: #1a1a1a; --strong: #2e2e2e; --signal: #e60012; --quote: #ffcc00; --btn: #e60012; --btn-fg: #ffffff; --well: #050505; --well-pad: 0.75rem; --phosphor: #ff1a2e; --live-edge: transparent; --glow: 0 0 12px color-mix(in srgb, var(--signal) 55%, transparent); --glow-soft: 0 0 8px color-mix(in srgb, var(--signal) 40%, transparent); --scan: repeating-linear-gradient( 0deg, rgb(255 255 255 / 0.03) 0 1px, transparent 1px 3px ); } } :root[data-theme="dark"] { --ground: #000000; --panel: #0a0a0a; --ink: #f2f2f2; --ink-muted: #9a9a9a; --ink-faint: #7d7d7d; --rule: #1a1a1a; --strong: #2e2e2e; --signal: #e60012; --quote: #ffcc00; --btn: #e60012; --btn-fg: #ffffff; --well: #050505; --well-pad: 0.75rem; --phosphor: #ff1a2e; --live-edge: transparent; --glow: 0 0 12px color-mix(in srgb, var(--signal) 55%, transparent); --glow-soft: 0 0 8px color-mix(in srgb, var(--signal) 40%, transparent); --scan: repeating-linear-gradient( 0deg, rgb(255 255 255 / 0.03) 0 1px, transparent 1px 3px ); } :root[data-theme="light"] { --ground: #f4f4f4; --panel: #ebebeb; --ink: #0a0a0a; --ink-muted: #444444; --ink-faint: #666666; --rule: #cccccc; --strong: #111111; --signal: #c40018; --quote: #9a6b00; --btn: #c40018; --btn-fg: #ffffff; --well: #f4f4f4; --well-pad: 0; --phosphor: #c40018; --glow: none; --glow-soft: none; --scan: none; --live-edge: var(--signal); } @theme inline { --color-ground: var(--ground); --color-panel: var(--panel); --color-ink: var(--ink); --color-ink-muted: var(--ink-muted); --color-ink-faint: var(--ink-faint); --color-rule: var(--rule); --color-strong: var(--strong); --color-signal: var(--signal); --color-quote: var(--quote); --color-btn: var(--btn); --color-btn-fg: var(--btn-fg); --color-well: var(--well); --color-phosphor: var(--phosphor); --font-display: "Chakra Petch", "Arial", "Helvetica Neue", sans-serif; } @utility clip-notch { /* Filled CTAs only (bg-btn etc.). Clip alone - solid fill defines the edge. Outline badges must use .wire-badge instead (border + clip-path eats strokes). */ clip-path: polygon( 0 0, calc(100% - 0.5rem) 0, 100% 0.5rem, 100% 100%, 0.5rem 100%, 0 calc(100% - 0.5rem) ); } @utility clip-notch-sm { /* Filled small controls only. Outline badges: use .wire-badge. */ clip-path: polygon(0 0, calc(100% - 5px) 0, 100% 5px, 100% 100%, 0 100%); } /* The newest wire entry sits on the well and reads in phosphor - the visible proof that the record is live. Scanlines are part of the well surface and disappear with it in light. Unlayered so it wins over utilities. */ #activity-wire { --wire-badge-fill: var(--ground); } /* Every row reserves the edge so live and stale rows stay aligned; only the live row paints it. In dark --live-edge is transparent because the well slab and the glow already say "live"; in light they both flatten to nothing, so the red edge is the only surface cue there is. */ #activity-wire article, #shoutbox-messages article { border-left: 2px solid transparent; } #activity-wire article:first-of-type { background-color: var(--well); background-image: var(--scan); border-left-color: var(--live-edge); /* Hollow badge interiors must match the well, not the page ground. */ --wire-badge-fill: var(--well); } #activity-wire article:first-of-type :is(span, time, a) { color: var(--phosphor); text-shadow: var(--glow-soft); } #activity-wire article:first-of-type .wire-badge { text-shadow: none; } /* The newest shout gets the same live treatment as the wire's newest entry. The log reads chronologically, so the newest message is the LAST article. Only the provenance line (handle + time) glows - shout bodies are user prose, not telemetry, and stay in ink. */ #shoutbox-messages article:last-of-type { background-color: var(--well); background-image: var(--scan); border-left-color: var(--live-edge); } #shoutbox-messages article:last-of-type :is(time, a) { color: var(--phosphor); text-shadow: var(--glow-soft); } /* Default hole colour for outline notched badges. Override on non-ground surfaces (e.g. #activity-wire article:first-of-type sets --well). */ :root { --wire-badge-fill: var(--ground); } .bg-panel { --wire-badge-fill: var(--panel); } /* Notched badge with a continuous stroke (top + diagonal included). Never use border + clip-path for outlines - clip-path eats the stroke. Shell = currentColor; hole = --wire-badge-fill; label above. */ .wire-badge { --wire-notch: 5px; --wire-stroke: 2px; position: relative; display: inline-flex; align-items: center; border: none !important; background-color: currentColor; clip-path: polygon( 0 0, calc(100% - var(--wire-notch)) 0, 100% var(--wire-notch), 100% 100%, 0 100% ); padding: var(--wire-stroke); isolation: isolate; } .wire-badge::before { content: ""; position: absolute; inset: var(--wire-stroke); z-index: 0; pointer-events: none; background: var(--wire-badge-fill, var(--ground)); clip-path: polygon( 0 0, calc(100% - max(0px, var(--wire-notch) - var(--wire-stroke))) 0, 100% max(0px, var(--wire-notch) - var(--wire-stroke)), 100% 100%, 0 100% ); } .wire-badge-label { position: relative; z-index: 1; display: inline-block; padding-inline: 0.375rem; padding-block: 0.125rem; color: inherit; line-height: 1.25; } /* Solid filled notched badges: slab only, no hollow. */ .wire-badge.bg-ink { background-color: var(--ink) !important; color: var(--ground); } .wire-badge.bg-signal { background-color: var(--signal) !important; color: var(--ground); } .wire-badge.bg-btn { background-color: var(--btn) !important; color: var(--btn-fg); } .wire-badge.bg-ink::before, .wire-badge.bg-signal::before, .wire-badge.bg-btn::before { display: none; } .wire-badge.bg-ink .wire-badge-label, .wire-badge.bg-signal .wire-badge-label { color: var(--ground); } .wire-badge.bg-btn .wire-badge-label { color: var(--btn-fg); } /* Theme toggle: the pressed appearance is derived from the same source of truth as the colours themselves - the `data-theme` attribute on - rather than from `aria-pressed`, which JavaScript can only set after the page has already painted. The server has no idea which theme a visitor chose, so it renders every button unpressed; without this the toggle visibly snapped to the right option a moment after load. Absent attribute means "follow the OS", which is the default, so that case needs no script at all. `aria-pressed` is still synced in JS because screen readers are unaffected by a paint-time flash. */ :root:not([data-theme]) [data-theme-option="system"], :root[data-theme="light"] [data-theme-option="light"], :root[data-theme="dark"] [data-theme-option="dark"] { background-color: var(--panel); color: var(--ink); }