/* =============================================================================
   components.css, SFDash design-system primitives
   =============================================================================

   Companion stylesheet to synoforce.css. Where synoforce.css defines TOKENS
   (colours, shadows, typography, Bootstrap variable overrides), this file
   defines COMPONENTS, the actual reusable class names the Twig templates
   and macros consume. Separating the two makes it obvious what is "brand
   skin" vs "reusable UI primitive", and means you can safely retheme the
   app by editing synoforce.css alone.

   What belongs here
   -----------------
     * Self-contained component classes that any page can reuse
       (.page-header, .stat-card, .empty-state, .status-dot, etc.).
     * Typography scale formalization (h1..h6 default sizes + semantic
       heading classes for card titles, section headers, etc.).
     * Utility classes that fill gaps in Bootstrap (aria-only visually
       hidden content, focus-ring helpers, etc.).

   What does NOT belong here
   -------------------------
     * Page-specific CSS. If a rule only applies to one template, keep
       it in that template's `{% block styles %}`, not here.
     * Overrides of SmartAdmin internals. Those go in synoforce.css
       because they depend on the CSS cascade ordering that file sets up.
     * Design tokens (colours, spacing, shadows). Those live in
       synoforce.css :root so both files can read them.

   All rules in this file reference CSS custom properties from
   synoforce.css (`var(--sf-*)`, `var(--bs-*)`) so dark-mode flipping
   keeps working: when the <html data-bs-theme> attribute changes, the
   tokens re-resolve and every component re-paints automatically.
   ============================================================================= */


/* -----------------------------------------------------------------------------
   Typography scale
   -----------------------------------------------------------------------------
   Codify a single heading scale for the entire dashboard. Templates have
   been drifting into h1 → h5 → h6 with no h2/h3 in between, which breaks
   the accessibility outline. The rule is:

     <h1>  page title                 , one per page, rendered by the layout
     <h2>  major section inside a page
     <h3>  card / subsection header
     <h4>  sub-subsection header
     <h5>  small label / pill header  , rarely needed
     <h6>  reserved for eyebrow / uppercase micro-labels

   Templates should prefer semantic elements (h1-h4) over utility classes
   (.h5, .h6). The .page-header partial sets the h1 automatically so
   templates don't have to.
*/
h1, .h1 {
    font-size: 1.75rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    line-height: 1.2;
    color: var(--bs-heading-color, var(--bs-body-color));
}
h2, .h2 {
    font-size: 1.375rem;
    font-weight: 600;
    line-height: 1.3;
    color: var(--bs-heading-color, var(--bs-body-color));
}
h3, .h3 {
    font-size: 1.125rem;
    font-weight: 600;
    line-height: 1.35;
    color: var(--bs-heading-color, var(--bs-body-color));
}
h4, .h4 {
    font-size: 1rem;
    font-weight: 600;
    line-height: 1.4;
}
h5, .h5 {
    font-size: 0.9375rem;
    font-weight: 600;
}
h6, .h6 {
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--sf-text-muted);
}


/* -----------------------------------------------------------------------------
   .page-header , canonical page title + subtitle + actions row
   -----------------------------------------------------------------------------
   Used by templates via the `components/_page_header.twig` macro. Renders
   a flex row with the page title on the left and an action-button slot
   on the right. Stacks vertically on mobile.
*/
.page-header {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.75rem 1.5rem;
    margin-bottom: 1.25rem;
    padding-bottom: 0.75rem;
    border-bottom: 1px solid var(--bs-border-color);
}
.page-header__text {
    min-width: 0; /* lets the title truncate rather than pushing the row wider */
}
.page-header__title {
    margin: 0;
}
.page-header__subtitle {
    margin: 0.125rem 0 0;
    color: var(--sf-text-muted);
    font-size: 0.875rem;
    font-weight: 400;
}
.page-header__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
    flex-shrink: 0;
}


/* -----------------------------------------------------------------------------
   .status-dot , small coloured circle for at-a-glance status
   -----------------------------------------------------------------------------
   Replaces ~20 inline `style="width:8px;height:8px;background:#..."`
   occurrences across templates. Pair with a visible label (or a title=""
   tooltip) , a dot alone fails accessibility for colour-blind users.
*/
.status-dot {
    display: inline-block;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    flex-shrink: 0;
    vertical-align: middle;
    background: var(--sf-disabled);
}
.status-dot--success  { background: var(--sf-success); }
.status-dot--warning  { background: var(--sf-warning); }
.status-dot--danger,
.status-dot--error    { background: var(--sf-error); }
.status-dot--info     { background: var(--sf-info); }
.status-dot--disabled { background: var(--sf-disabled); }
.status-dot--muted    { background: var(--sf-text-muted); }

/* A subtle animated pulse for "live" indicators (e.g. currently-running
   backup, monitoring probe in-flight). Uses CSS animation; respects the
   user's `prefers-reduced-motion` setting below. */
.status-dot--pulse {
    position: relative;
}
.status-dot--pulse::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: inherit;
    opacity: 0.6;
    animation: status-dot-pulse 1.6s ease-out infinite;
}
@keyframes status-dot-pulse {
    0%   { transform: scale(1);   opacity: 0.6; }
    70%  { transform: scale(2.4); opacity: 0;   }
    100% { transform: scale(2.4); opacity: 0;   }
}


/* -----------------------------------------------------------------------------
   .stat-card , dashboard metric tile
   -----------------------------------------------------------------------------
   Replaces the 6x copy-pasted stat tile pattern on the dashboard home page.
   Used via the `components/_stat_card.twig` macro.
*/
.stat-card {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    padding: 1rem 1.25rem;
    border: 1px solid var(--bs-border-color);
    border-radius: var(--sf-radius);
    background: var(--bs-body-bg);
    box-shadow: var(--sf-shadow-1);
    transition: box-shadow 120ms ease;
    height: 100%;
}
.stat-card:hover {
    box-shadow: var(--sf-shadow-2);
}
.stat-card__label {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--sf-text-muted);
    font-weight: 600;
}
.stat-card__value {
    font-size: 1.625rem;
    font-weight: 700;
    line-height: 1.1;
    color: var(--bs-heading-color, var(--bs-body-color));
}
.stat-card__trend {
    font-size: 0.75rem;
    color: var(--sf-text-muted);
}
.stat-card__trend--up    { color: var(--sf-success); }
.stat-card__trend--down  { color: var(--sf-error); }
.stat-card--primary { border-top: 3px solid var(--sf-primary); }
.stat-card--success { border-top: 3px solid var(--sf-success); }
.stat-card--warning { border-top: 3px solid var(--sf-warning); }
.stat-card--danger  { border-top: 3px solid var(--sf-error); }


/* -----------------------------------------------------------------------------
   .empty-state , unified "no data yet" placeholder
   -----------------------------------------------------------------------------
   Current templates roll their own empty state every time, leading to
   inconsistent copy, spacing, and tone. This primitive gives every
   page the same zero-data treatment + an optional CTA.
*/
.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 0.75rem;
    padding: 2.5rem 1rem;
    color: var(--sf-text-muted);
}
.empty-state__icon {
    width: 3rem;
    height: 3rem;
    opacity: 0.6;
}
.empty-state__title {
    font-size: 1rem;
    font-weight: 600;
    color: var(--bs-body-color);
    margin: 0;
}
.empty-state__description {
    max-width: 32rem;
    font-size: 0.875rem;
    margin: 0;
}
.empty-state__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 0.25rem;
}


/* -----------------------------------------------------------------------------
   .form-field , consistent label/input/help/error wrapping
   -----------------------------------------------------------------------------
   Used by the `components/_form_field.twig` macro. Templates no longer
   need to hand-roll the label + input + help + error markup per page.
*/
.form-field {
    display: block;
    margin-bottom: 1rem;
}
.form-field__label {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    margin-bottom: 0.25rem;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--bs-body-color);
}
.form-field__required {
    color: var(--sf-error);
    font-weight: 700;
    margin-left: 0.125rem;
}
.form-field__help {
    margin-top: 0.25rem;
    font-size: 0.75rem;
    color: var(--sf-text-muted);
}
.form-field__error {
    margin-top: 0.25rem;
    font-size: 0.75rem;
    color: var(--sf-error);
}
.form-field--optional .form-field__label::after {
    content: " (optional)";
    color: var(--sf-text-muted);
    font-weight: 400;
}
/* Pair with a `.form-legend` above a form for "* = required field" copy. */
.form-legend {
    font-size: 0.75rem;
    color: var(--sf-text-muted);
    margin-bottom: 0.75rem;
}
.form-legend__asterisk {
    color: var(--sf-error);
    font-weight: 700;
}


/* -----------------------------------------------------------------------------
   .status-badge , accessible status pill with optional icon
   -----------------------------------------------------------------------------
   Companion to status-dot for places where a longer label is wanted.
   Uses Bootstrap's text/background colour utilities under the hood but
   provides a consistent shape, padding, and icon slot.
*/
.status-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.125rem 0.625rem;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 600;
    line-height: 1.5;
    white-space: nowrap;
}
.status-badge__icon {
    width: 0.75rem;
    height: 0.75rem;
    flex-shrink: 0;
}
.status-badge--success { background: rgba(16, 185, 129, 0.12); color: var(--sf-success); }
.status-badge--warning { background: rgba(245, 158, 11, 0.14); color: var(--sf-warning); }
.status-badge--danger  { background: rgba(239, 68, 68, 0.12);  color: var(--sf-error);   }
.status-badge--info    { background: rgba(59, 130, 246, 0.12); color: var(--sf-info);    }
.status-badge--muted   { background: rgba(107, 114, 128, 0.12); color: var(--sf-text-muted); }


/* -----------------------------------------------------------------------------
   .badge-dynamic , for pills with admin-configured hex background.
   -----------------------------------------------------------------------------
   Helpdesk statuses, priorities, etc. let admins pick any hex colour. A pale
   yellow (#ffd) bg reads terribly with white text, a dark navy (#012) reads
   terribly with black text. This class forces legible white text and paints
   a 1px text-shadow so the glyph has a contrast anchor regardless of bg.
*/
.badge-dynamic {
    color: #fff;
    text-shadow: 0 0 2px rgba(0, 0, 0, 0.55);
}


/* -----------------------------------------------------------------------------
   .theme-toggle , three-state pill (light / auto / dark)
   -----------------------------------------------------------------------------
   Used by the operator topbar (app_layout.twig). The client portal has its
   own scoped copy inline inside portal_layout.twig (historical reason —
   portal was built before this design-system file existed); the shapes and
   behaviour are identical. Keep the two in sync if you change visuals here.

   Active button uses a mixed-transparency brand tint so it reads as a pill
   fill without fighting the surrounding chrome. Sized to sit comfortably on
   the operator header (28×28 per button) without pushing other controls.
*/
.theme-toggle {
    display: inline-flex;
    align-items: center;
    border: 1px solid var(--bs-border-color, rgba(0,0,0,0.15));
    border-radius: 999px;
    padding: 2px;
    background: var(--bs-body-bg, transparent);
    line-height: 1;
}
.theme-toggle button {
    appearance: none;
    border: 0;
    background: transparent;
    color: var(--bs-secondary-color, #6c757d);
    width: 30px;
    height: 28px;
    border-radius: 999px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    padding: 0;
    transition: background-color .15s ease, color .15s ease;
}
.theme-toggle button:hover {
    color: var(--bs-body-color);
}
.theme-toggle button[aria-pressed="true"] {
    background: color-mix(in srgb, var(--bs-primary, #0d6efd) 18%, transparent);
    color:      var(--bs-primary, #0d6efd);
}
[data-bs-theme="dark"] .theme-toggle button[aria-pressed="true"] {
    color: #7FD0FE;
}
.theme-toggle .sa-icon {
    width: 16px;
    height: 16px;
}


/* -----------------------------------------------------------------------------
   .breadcrumbs , compact "you are here" row
   -----------------------------------------------------------------------------
   Used by `components/_breadcrumbs.twig`. Rendered just below the main
   header, above the page title. Kept tiny on purpose , breadcrumbs
   are supposed to help orient, not distract.
*/
.breadcrumbs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem;
    margin-bottom: 0.5rem;
    font-size: 0.75rem;
    color: var(--sf-text-muted);
}
.breadcrumbs__item {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
}
.breadcrumbs__separator {
    opacity: 0.5;
}
.breadcrumbs__link {
    color: var(--sf-text-muted);
    text-decoration: none;
}
.breadcrumbs__link:hover {
    color: var(--sf-primary);
    text-decoration: underline;
}
.breadcrumbs__current {
    color: var(--bs-body-color);
    font-weight: 600;
}


/* -----------------------------------------------------------------------------
   Contextual table row classes in dark mode
   -----------------------------------------------------------------------------
   Bootstrap 5's `.table-danger`, `.table-warning`, `.table-success`, and
   `.table-info` bake their palette into per-class custom properties:

       .table-danger { --bs-table-bg: #f8d7da; --bs-table-color: #000; ... }

   Those values are hardcoded in bootstrap.min.css and do NOT flip when
   the app switches to `data-bs-theme="dark"`. The result is a white-ish
   row with black text sitting on top of the dark page background, which
   is exactly what "Domains expiring" looks like today: a bright slab of
   stale Bootstrap defaults cutting through the dark theme.

   Re-point the custom properties for dark mode so the contextual rows
   use a DARK tinted background + the normal dark-mode text colour. We
   keep the same hue family (red for danger, amber for warning, etc.)
   but dial the lightness way down and raise the alpha so the tint is
   still clearly visible against the navy page surface.

   Scoped to `[data-bs-theme="dark"] .table-*` so light mode stays on
   the Bootstrap defaults, which are actually fine there.
*/
[data-bs-theme="dark"] .table > :not(caption) > * > * {
    /* Inherit the dark table text colour by default. Individual
       contextual classes below override as needed. */
    color: var(--bs-body-color);
}

[data-bs-theme="dark"] .table-danger {
    --bs-table-color:            #fecaca;
    --bs-table-bg:               rgba(239, 68, 68, 0.18);
    --bs-table-border-color:     rgba(239, 68, 68, 0.35);
    --bs-table-striped-bg:       rgba(239, 68, 68, 0.24);
    --bs-table-striped-color:    #fecaca;
    --bs-table-active-bg:        rgba(239, 68, 68, 0.30);
    --bs-table-active-color:     #fecaca;
    --bs-table-hover-bg:         rgba(239, 68, 68, 0.24);
    --bs-table-hover-color:      #fecaca;
    color:                       var(--bs-table-color);
    border-color:                var(--bs-table-border-color);
}

[data-bs-theme="dark"] .table-warning {
    --bs-table-color:            #fde68a;
    --bs-table-bg:               rgba(245, 158, 11, 0.18);
    --bs-table-border-color:     rgba(245, 158, 11, 0.35);
    --bs-table-striped-bg:       rgba(245, 158, 11, 0.24);
    --bs-table-striped-color:    #fde68a;
    --bs-table-active-bg:        rgba(245, 158, 11, 0.30);
    --bs-table-active-color:     #fde68a;
    --bs-table-hover-bg:         rgba(245, 158, 11, 0.24);
    --bs-table-hover-color:      #fde68a;
    color:                       var(--bs-table-color);
    border-color:                var(--bs-table-border-color);
}

[data-bs-theme="dark"] .table-success {
    --bs-table-color:            #bbf7d0;
    --bs-table-bg:               rgba(16, 185, 129, 0.18);
    --bs-table-border-color:     rgba(16, 185, 129, 0.35);
    --bs-table-striped-bg:       rgba(16, 185, 129, 0.24);
    --bs-table-striped-color:    #bbf7d0;
    --bs-table-active-bg:        rgba(16, 185, 129, 0.30);
    --bs-table-active-color:     #bbf7d0;
    --bs-table-hover-bg:         rgba(16, 185, 129, 0.24);
    --bs-table-hover-color:      #bbf7d0;
    color:                       var(--bs-table-color);
    border-color:                var(--bs-table-border-color);
}

[data-bs-theme="dark"] .table-info {
    --bs-table-color:            #bfdbfe;
    --bs-table-bg:               rgba(59, 130, 246, 0.18);
    --bs-table-border-color:     rgba(59, 130, 246, 0.35);
    --bs-table-striped-bg:       rgba(59, 130, 246, 0.24);
    --bs-table-striped-color:    #bfdbfe;
    --bs-table-active-bg:        rgba(59, 130, 246, 0.30);
    --bs-table-active-color:     #bfdbfe;
    --bs-table-hover-bg:         rgba(59, 130, 246, 0.24);
    --bs-table-hover-color:      #bfdbfe;
    color:                       var(--bs-table-color);
    border-color:                var(--bs-table-border-color);
}

[data-bs-theme="dark"] .table-primary {
    --bs-table-color:            #bae6fd;
    --bs-table-bg:               rgba(1, 162, 253, 0.18);
    --bs-table-border-color:     rgba(1, 162, 253, 0.35);
    --bs-table-striped-bg:       rgba(1, 162, 253, 0.24);
    --bs-table-striped-color:    #bae6fd;
    --bs-table-active-bg:        rgba(1, 162, 253, 0.30);
    --bs-table-active-color:     #bae6fd;
    --bs-table-hover-bg:         rgba(1, 162, 253, 0.24);
    --bs-table-hover-color:      #bae6fd;
    color:                       var(--bs-table-color);
    border-color:                var(--bs-table-border-color);
}

[data-bs-theme="dark"] .table-secondary {
    --bs-table-color:            #e5e7eb;
    --bs-table-bg:               rgba(107, 114, 128, 0.22);
    --bs-table-border-color:     rgba(107, 114, 128, 0.38);
    --bs-table-striped-bg:       rgba(107, 114, 128, 0.28);
    --bs-table-striped-color:    #e5e7eb;
    --bs-table-active-bg:        rgba(107, 114, 128, 0.34);
    --bs-table-active-color:     #e5e7eb;
    --bs-table-hover-bg:         rgba(107, 114, 128, 0.28);
    --bs-table-hover-color:      #e5e7eb;
    color:                       var(--bs-table-color);
    border-color:                var(--bs-table-border-color);
}


/* -----------------------------------------------------------------------------
   .from-to-diff , shared before→after diff renderer
   -----------------------------------------------------------------------------
   Emitted by `components/_from_to_diff.twig`. Used by the Change history
   panel on zone detail pages AND by the global audit log to render rrset
   mutations with a line-by-line content diff + metadata delta rows. Styled
   once here so both call sites look identical in light + dark mode.
*/
/* diff-block / diff-line are used both inside .from-to-diff
   (the shared macro) and standalone for rename banners in the
   Change history panel + global audit log. No parent scope. */
.diff-block {
    background: var(--bs-tertiary-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--sf-radius, 0.5rem);
    padding: 0.5rem 0.625rem;
    font-size: 0.72rem;
    line-height: 1.5;
}
.diff-line {
    white-space: pre-wrap;
    word-break: break-all;
}
.diff-line--removed { color: var(--bs-danger);  }
.diff-line--added   { color: var(--bs-success); }
.diff-line--context { color: var(--bs-body-color); opacity: 0.75; }
.from-to-diff .diff-meta {
    font-size: 0.72rem;
}
.from-to-diff .diff-meta dt {
    color: var(--bs-secondary-color);
    font-size: 0.65rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-weight: 600;
}
.from-to-diff .diff-meta dd {
    color: var(--bs-body-color);
    margin-bottom: 0.25rem;
}


/* -----------------------------------------------------------------------------
   .user-mention , @tag handle rendering
   -----------------------------------------------------------------------------
   Used by the `_user_mention.twig` macro for every "who did this" slot:
   change history rows, audit log, client-user cards, group members, etc.
   Keeps the visual weight consistent with Bootstrap links but slightly
   tighter so @tags don't compete with content labels for attention.

   The span and anchor forms share the same ruleset so switching `link=false`
   in the macro doesn't visually shift the row.
*/
.user-mention {
    color: var(--bs-link-color, var(--sf-primary));
    text-decoration: none;
    font-weight: 500;
}
.user-mention:hover {
    color: var(--bs-link-hover-color, var(--sf-primary-light));
    text-decoration: underline;
}
span.user-mention {
    /* Plain-text mention inside a wrapping anchor: still themed but
       no hover underline so we don't suggest a double-click target. */
    color: inherit;
    font-weight: 500;
}


/* -----------------------------------------------------------------------------
   Focus ring helper
   -----------------------------------------------------------------------------
   Bootstrap's focus ring is on by default for form controls, but links
   and custom clickable elements frequently lose it when templates set
   `outline:none`. The .focus-ring utility guarantees an accessible
   keyboard focus outline without touching the default mouse hover state.
*/
.focus-ring:focus-visible {
    outline: 2px solid var(--bs-focus-ring-color, rgba(1, 162, 253, 0.5));
    outline-offset: 2px;
    border-radius: var(--sf-radius);
}


/* -----------------------------------------------------------------------------
   prefers-reduced-motion
   -----------------------------------------------------------------------------
   Respect the user's OS setting. Components that animate (pulse dot,
   card hover shadow) fall back to static rendering when motion is
   reduced. Bootstrap already respects this for its own animations,
   but our custom keyframes need an explicit opt-out.
*/
@media (prefers-reduced-motion: reduce) {
    .status-dot--pulse::after {
        animation: none;
    }
    .stat-card {
        transition: none;
    }
}


/* -----------------------------------------------------------------------------
   Skip link , keyboard users can jump past the sidebar to the main area
   -----------------------------------------------------------------------------
   Visually hidden until focused. Pairs with an `id="main-content"`
   attribute on the main page region in app_layout.twig.
*/
.skip-link {
    position: absolute;
    top: -40px;
    left: 1rem;
    z-index: 1080; /* above Bootstrap modals */
    padding: 0.5rem 0.875rem;
    background: var(--sf-primary);
    color: #fff;
    font-size: 0.8125rem;
    font-weight: 600;
    border-radius: var(--sf-radius);
    text-decoration: none;
    transition: top 100ms ease-out;
}
.skip-link:focus {
    top: 1rem;
}
