/* ============================================
   Panel Showcase
   ============================================ */
.panel-showcase {
    padding: 0 0 20px;
    background: linear-gradient(to bottom, var(--bg-alt) 50%, var(--bg) 50%);
}
.mock-browser {
    max-width: 980px;
    margin: 0 auto;
    border-radius: var(--r-xl);
    overflow: hidden;
    box-shadow: 0 8px 60px rgba(0,0,0,0.12), 0 2px 8px rgba(0,0,0,0.06);
    border: 1px solid var(--border);
    transform: perspective(1200px) rotateX(6deg) scale(0.97);
    transform-origin: center bottom;
    transition: transform 0.6s var(--ease), box-shadow 0.6s var(--ease);
}
.mock-browser.reveal {
    opacity: 0;
    transform: perspective(1200px) rotateX(16deg) scale(0.92);
    transition: opacity 0.8s var(--ease), transform 0.8s var(--ease), box-shadow 0.6s var(--ease);
}
.mock-browser.reveal.visible {
    opacity: 1;
    transform: perspective(1200px) rotateX(0deg) scale(1);
}
.mock-browser.reveal.visible:hover {
    transform: perspective(1200px) rotateX(0deg) scale(1);
    box-shadow: 0 16px 80px rgba(0,0,0,0.15), 0 4px 12px rgba(0,0,0,0.08);
}
.mock-browser:hover {
    transform: perspective(1200px) rotateX(0deg) scale(1);
    box-shadow: 0 16px 80px rgba(0,0,0,0.15), 0 4px 12px rgba(0,0,0,0.08);
}
.mock-chrome {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    padding: 10px 16px;
    background: #f8f8f8;
    border-bottom: 1px solid var(--border);
}
.mock-chrome-dots {
    display: flex;
    gap: 6px;
}
.mock-chrome-dots span {
    width: 10px;
    height: 10px;
    border-radius: 50%;
}
.mock-chrome-dots span:nth-child(1) { background: #ff5f57; }
.mock-chrome-dots span:nth-child(2) { background: #febc2e; }
.mock-chrome-dots span:nth-child(3) { background: #28c840; }
.mock-chrome-bar {
    flex: 1;
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 5px 12px;
    background: #fff;
    border-radius: 6px;
    font-size: 11px;
    color: #999;
    border: none;
}
.mock-chrome-bar svg { color: #999; flex-shrink: 0; }

/* Panel body layout */
.mock-panel {
    display: flex;
    min-height: 480px;
}

/* Sidebar */
.mock-sidebar {
    width: 150px;
    background: #1a1a2e;
    padding: 14px 0;
    display: flex;
    flex-direction: column;
    flex-shrink: 0;
}
.mock-logo { margin: 0 0 16px 14px; }
.mock-nav {
    display: flex;
    flex-direction: column;
    gap: 2px;
    width: 100%;
    padding: 0 8px;
}
.mock-nav-item {
    display: flex;
    align-items: center;
    /* Explicit, because several browsers' UA stylesheets centre the content of
       a <button>; without this the icons and labels can fail to form a column. */
    justify-content: flex-start;
    gap: 9px;
    width: 100%;
    height: 32px;
    padding: 0 9px;
    border: none;
    background: none;
    font: inherit;
    font-size: 11px;
    font-weight: 600;
    text-align: left;
    border-radius: 8px;
    color: rgba(255,255,255,0.45);
    cursor: pointer;
    transition: color 0.2s ease, background 0.2s ease;
    position: relative;
    overflow: hidden;
}
.mock-nav-icon {
    display: flex;
    flex-shrink: 0;
    transform-origin: center;
}
.mock-nav-item svg { width: 15px; height: 15px; }

/* Selecting a section gives its icon a short spring, so the eye is pulled to
   the item that just became active. The class is toggled on every selection,
   which restarts the animation each time, including on each auto-tour step.
   Disabled under prefers-reduced-motion by the block at the end of this file. */
@keyframes mockNavIconPop {
    0%   { transform: scale(1)    rotate(0deg); }
    40%  { transform: scale(1.3)  rotate(-7deg); }
    70%  { transform: scale(0.93) rotate(4deg); }
    100% { transform: scale(1)    rotate(0deg); }
}
.mock-nav-item.active .mock-nav-icon {
    animation: mockNavIconPop 460ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
.mock-nav-label {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.mock-nav-item:hover { color: rgba(255,255,255,0.9); background: rgba(255,255,255,0.08); }
.mock-nav-item:focus-visible {
    outline: 2px solid #e23744;
    outline-offset: -2px;
}
.mock-nav-item.active { color: #fff; background: rgba(226,55,68,0.2); }
.mock-nav-item.active::before {
    content: '';
    position: absolute;
    left: -8px;
    top: 50%;
    transform: translateY(-50%);
    width: 3px;
    height: 18px;
    background: #e23744;
    border-radius: 0 3px 3px 0;
}

/* Main area */
.mock-main {
    flex: 1;
    background: #fafafa;
    display: flex;
    flex-direction: column;
    min-width: 0;
}
.mock-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 20px;
    background: linear-gradient(135deg, #e23744 0%, #ff6b4a 50%, #ff9e1d 100%);
}
.mock-header-logo {
    height: 14px;
    width: auto;
    filter: brightness(0) invert(1);
    flex-shrink: 0;
}
.mock-greeting {
    font-size: 11px;
    color: rgba(255,255,255,0.85);
    white-space: nowrap;
}
.mock-search {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 5px 10px;
    background: rgba(255,255,255,0.15);
    border-radius: 6px;
    font-size: 11px;
    color: rgba(255,255,255,0.6);
    min-width: 180px;
}
.mock-search svg {
    color: rgba(255,255,255,0.6);
}
.mock-search-text {
    color: #fff;
}
.mock-cursor {
    display: inline-block;
    width: 1px;
    height: 13px;
    background: rgba(255,255,255,0.6);
    animation: mockBlink 1s step-end infinite;
    vertical-align: middle;
}
@keyframes mockBlink {
    0%, 100% { opacity: 1; }
    50% { opacity: 0; }
}
.mock-header-right {
    display: flex;
    align-items: center;
    gap: 12px;
}
.mock-avatar {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    overflow: hidden;
    cursor: pointer;
}
.mock-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Content */
.mock-content {
    padding: 16px 20px;
    flex: 1;
}
.mock-stats {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 10px;
    margin-bottom: 14px;
}
.mock-stat {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px;
    background: #fff;
    border-radius: 10px;
    border: 1px solid #efefef;
    cursor: pointer;
    transition: all 0.2s ease;
}
.mock-stat:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0,0,0,0.06);
    border-color: var(--border-hover);
}
.mock-stat-icon {
    width: 32px;
    height: 32px;
    border-radius: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.mock-stat-icon svg {
    width: 15px;
    height: 15px;
}
.mock-stat[data-color="primary"] .mock-stat-icon {
    background: #fde8ea;
    color: #e23744;
}
.mock-stat[data-color="success"] .mock-stat-icon {
    background: #dcfce7;
    color: #16a34a;
}
.mock-stat[data-color="info"] .mock-stat-icon {
    background: #e0f2fe;
    color: #0284c7;
}
.mock-stat[data-color="warning"] .mock-stat-icon {
    background: #fefce8;
    color: #ca8a04;
}
.mock-stat-info {
    display: flex;
    flex-direction: column;
    min-width: 0;
}
.mock-stat-value {
    font-family: var(--font-display);
    font-size: 17px;
    font-weight: 700;
    color: var(--text);
    line-height: 1.1;
}
.mock-stat-label {
    font-size: 10px;
    color: var(--text-muted);
    white-space: nowrap;
}

/* Dashboard grid */
.mock-grid {
    display: grid;
    grid-template-columns: 1.2fr 1fr;
    gap: 10px;
}
.mock-card {
    background: #fff;
    border-radius: 10px;
    border: 1px solid #efefef;
    overflow: hidden;
}
.mock-card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 14px;
    border-bottom: 1px solid #f3f3f3;
    font-size: 12px;
    font-weight: 600;
    color: var(--text);
}
.mock-card-link {
    font-size: 10px;
    font-weight: 500;
    color: var(--red);
    cursor: pointer;
}

/* Mock CWV card */
.mock-cwv-metrics {
    display: flex;
    gap: 0;
}
.mock-cwv-metric {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 12px 8px;
    border-right: 1px solid #f3f3f3;
    opacity: 0;
    transform: translateY(8px);
    transition: opacity 0.4s ease, transform 0.4s ease;
}
.mock-cwv-metric:last-child { border-right: none; }
.mock-browser.animated .mock-cwv-metric:nth-child(1) { opacity: 1; transform: translateY(0); transition-delay: 0.3s; }
.mock-browser.animated .mock-cwv-metric:nth-child(2) { opacity: 1; transform: translateY(0); transition-delay: 0.5s; }
.mock-browser.animated .mock-cwv-metric:nth-child(3) { opacity: 1; transform: translateY(0); transition-delay: 0.7s; }
.mock-cwv-ring {
    position: relative;
    width: 40px;
    height: 40px;
    flex-shrink: 0;
}
.mock-cwv-ring svg { width: 100%; height: 100%; }
.mock-cwv-progress {
    stroke-dasharray: 125.66;
    stroke-dashoffset: 125.66;
    transition: stroke-dashoffset 1s ease;
}
.mock-cwv-ring-label {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 8px;
    font-weight: 700;
    color: var(--text);
}
.mock-cwv-detail {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1px;
    min-width: 0;
}
.mock-cwv-name {
    font-size: 8px;
    color: #888;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.mock-cwv-value {
    font-size: 13px;
    font-weight: 700;
}
.mock-cwv-good { color: #0cce6b; }

/* Mock Backup card */
.mock-backup-list {
    display: flex;
    flex-direction: column;
}
.mock-backup-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    border-bottom: 1px solid #f3f3f3;
    opacity: 0;
    transform: translateX(-8px);
    transition: opacity 0.4s ease, transform 0.4s ease;
}
.mock-backup-row:last-child { border-bottom: none; }
.mock-browser.animated .mock-backup-row:nth-child(1) { opacity: 1; transform: translateX(0); transition-delay: 0.4s; }
.mock-browser.animated .mock-backup-row:nth-child(2) { opacity: 1; transform: translateX(0); transition-delay: 0.6s; }
.mock-browser.animated .mock-backup-row:nth-child(3) { opacity: 1; transform: translateX(0); transition-delay: 0.8s; }
.mock-backup-icon {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.mock-backup-icon svg { width: 12px; height: 12px; }
.mock-backup-success { background: #e6f9ee; color: #0cce6b; }
.mock-backup-scheduled { background: #e8f0fe; color: #4a90d9; }
.mock-backup-info {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}
.mock-backup-name { font-size: 11px; font-weight: 600; color: var(--text); }
.mock-backup-meta { font-size: 9px; color: #999; }
.mock-badge-blue { background: #e8f0fe; color: #4a90d9; }

/* Mock AI card */
.mock-ai-credits {
    font-size: 9px;
    font-weight: 500;
    color: #999;
}
.mock-ai-content {
    padding: 10px 14px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.mock-ai-prompt {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 10px;
    background: #f5f5f5;
    border-radius: 6px;
    font-size: 10px;
    color: #555;
}
.mock-ai-prompt svg { color: var(--red); flex-shrink: 0; }
.mock-ai-cursor {
    display: inline-block;
    width: 1px;
    height: 11px;
    background: #999;
    animation: mockBlink 1s step-end infinite;
    vertical-align: middle;
}
.mock-ai-response {
    display: flex;
    flex-direction: column;
    gap: 6px;
    opacity: 0;
    transition: opacity 0.5s ease;
}
.mock-browser.animated .mock-ai-response { opacity: 1; transition-delay: 1.8s; }
.mock-ai-line {
    height: 6px;
    border-radius: 3px;
    background: linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%);
    background-size: 200% 100%;
    animation: mockShimmer 1.5s ease infinite;
}
.mock-browser.animated .mock-ai-line {
    background: #f0f0f0;
    animation: none;
}
@keyframes mockShimmer {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

/* Mock table */
.mock-table {
    font-size: 10px;
}
.mock-table-head {
    display: grid;
    grid-template-columns: 2fr 1.2fr 0.8fr 0.6fr;
    gap: 6px;
    padding: 8px 14px;
    color: var(--text-muted);
    font-weight: 600;
    text-transform: uppercase;
    font-size: 9px;
    letter-spacing: 0.04em;
    border-bottom: 1px solid #f5f5f5;
}
.mock-table-row {
    display: grid;
    grid-template-columns: 2fr 1.2fr 0.8fr 0.6fr;
    gap: 6px;
    padding: 8px 14px;
    color: var(--text-secondary);
    border-bottom: 1px solid #fafafa;
    opacity: 0;
    transform: translateY(6px);
    transition: all 0.3s ease;
}
.mock-table-row.visible {
    opacity: 1;
    transform: translateY(0);
}
.mock-key {
    font-family: 'IBM Plex Mono', monospace;
    font-size: 10px;
    color: var(--text);
    font-weight: 500;
}
.mock-badge {
    font-style: normal;
    font-size: 9px;
    font-weight: 600;
    padding: 2px 7px;
    border-radius: 10px;
}
.mock-badge-green {
    background: #dcfce7;
    color: #16a34a;
}

/* Mock chart */
.mock-analytics-tabs {
    display: flex;
    gap: 2px;
    background: #f3f4f6;
    border-radius: 5px;
    padding: 2px;
}
.mock-analytics-tab {
    font-size: 8px;
    font-weight: 600;
    padding: 2px 8px;
    border-radius: 4px;
    color: #9ca3af;
    cursor: default;
}
.mock-analytics-tab--active {
    background: #fff;
    color: #374151;
    box-shadow: 0 1px 2px rgba(0,0,0,0.06);
}
.mock-analytics-kpis {
    display: flex;
    gap: 8px;
    padding: 8px 14px 0;
}
.mock-analytics-kpi { flex: 1; }
.mock-analytics-kpi-val {
    display: block;
    font-size: 13px;
    font-weight: 800;
    letter-spacing: -0.02em;
    color: #171717;
    line-height: 1.2;
}
.mock-analytics-kpi-up { color: #16a34a; }
.mock-analytics-kpi-label {
    font-size: 8px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #a3a3a3;
}
.mock-chart {
    padding: 6px 14px 14px;
    position: relative;
}
.mock-chart-svg {
    width: 100%;
    height: 110px;
    display: block;
}
.mock-live-dot {
    position: absolute;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #3b82f6;
    box-shadow: 0 0 0 2px rgba(59,130,246,0.2);
    right: 14px;
    top: 6px;
    opacity: 0;
    transition: top 0.5s cubic-bezier(0.16,1,0.3,1), opacity 0.3s ease;
}
.mock-live-dot.visible {
    opacity: 1;
    animation: mockDotPulse 2s ease infinite;
}
@keyframes mockDotPulse {
    0%,100% { box-shadow: 0 0 0 2px rgba(59,130,246,0.2); }
    50% { box-shadow: 0 0 0 5px rgba(59,130,246,0.06); }
}
/* Analytics-only: this chart ticks roughly half as often as the dashboard's
   (see tickMs in panel-mock.js), so the trailing dot's own move-to-new-spot
   transition is slowed by about the same ratio. Left as the plain
   .mock-live-dot timing everywhere else (the dashboard chart is untouched). */
.mock-live-dot--calm { transition: top 1.1s cubic-bezier(0.16,1,0.3,1), opacity 0.3s ease; }

/* Analytics chart gridlines (Analytics view only; the dashboard's compact
   chart never sets the grid option and never renders these). Hairline
   weight, low opacity, and always behind the data because panel-mock.js
   appends this markup before the area/line paths in source order. */
.mock-chart-grid-line { stroke: rgba(0,0,0,0.08); stroke-width: 1; }
[data-theme="dark"] .mock-chart-grid-line { stroke: rgba(255,255,255,0.12); }

/* Analytics chart value/weekday labels (Analytics view only). Real HTML,
   not SVG <text>: the chart's viewBox is stretched horizontally only
   (width:100% against a literal, unstretched pixel height, since
   preserveAspectRatio is "none"), so glyphs placed inside the SVG's own
   coordinate system get squeezed horizontally at any rendered width other
   than the exact viewBox width, visibly condensed at narrow breakpoints.
   `.mock-chart-labels` is an absolutely positioned overlay sized to match
   the chart exactly (see buildHtmlLabels() in panel-mock.js, same technique
   as `.mock-live-dot` above), so labels lay out in real, undistorted CSS
   pixels; percentages inside it still line up with each gridline/tick
   because the overlay's own box is pinned 1:1 to the SVG's box.
   pointer-events:none keeps the whole overlay out of the way of clicks and
   text selection, since it sits visually on top of the chart. */
.mock-chart-labels {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    pointer-events: none;
}
.mock-chart-grid-label,
.mock-chart-axis-label {
    position: absolute;
    font-size: 8px;
    font-family: var(--font-body);
    white-space: nowrap;
    pointer-events: none;
    user-select: none;
}
/* Value labels: nudged in from the chart's left edge, sitting just above
   their own gridline (translateY(-100%) anchors the label's bottom edge,
   not its top, at that percentage). They sit inside the plot area itself
   rather than a reserved gutter, so the data line can pass directly behind
   one; a text-shadow halo in the chart's own flat background colour (CSS
   has no paint-order for HTML text, so this is the closest equivalent to an
   SVG stroke halo) keeps each label legible without a filled box, which
   would read as a heavier UI element than a number sitting quietly over a
   trend line. A touch more weight than the axis labels below gives the
   numbers enough contrast to hold their own now that they sit over the plot
   instead of beside it, while staying visibly subordinate to the data
   strokes. */
.mock-chart-grid-label {
    left: 6px;
    color: #767676;
    transform: translateY(-100%);
    text-shadow:
        -1px -1px 0 #fafafa, 1px -1px 0 #fafafa,
        -1px  1px 0 #fafafa, 1px  1px 0 #fafafa,
        0 0 3px #fafafa;
}
/* Weekday labels: centred under their tick, in the axis band below the
   plot. They never sit over the data, so no halo is needed here. */
.mock-chart-axis-label {
    bottom: 3px;
    color: #999;
    transform: translateX(-50%);
}
[data-theme="dark"] .mock-chart-grid-label {
    color: #b8b8b8;
    text-shadow:
        -1px -1px 0 #141414, 1px -1px 0 #141414,
        -1px  1px 0 #141414, 1px  1px 0 #141414,
        0 0 3px #141414;
}
[data-theme="dark"] .mock-chart-axis-label { color: #8a8a8a; }

/* Mock browser panel — dark mode */
[data-theme="dark"] .mock-chrome {
    background: #1a1a1a;
    border-bottom-color: #2a2a2a;
}
[data-theme="dark"] .mock-chrome-bar {
    background: #0f0f0f;
    color: #666;
}
[data-theme="dark"] .mock-chrome-bar svg {
    color: #666;
}
[data-theme="dark"] .mock-sidebar {
    background: #111118;
}
[data-theme="dark"] .mock-main {
    background: #141414;
}
[data-theme="dark"] .mock-content {
    background: #141414;
}
[data-theme="dark"] .mock-stat {
    background: #1a1a1a;
    border-color: #2a2a2a;
}
[data-theme="dark"] .mock-stat:hover {
    box-shadow: 0 4px 12px rgba(0,0,0,0.3);
    border-color: #3a3a3a;
}
[data-theme="dark"] .mock-stat[data-color="primary"] .mock-stat-icon {
    background: rgba(226,55,68,0.15);
}
[data-theme="dark"] .mock-stat[data-color="success"] .mock-stat-icon {
    background: rgba(22,163,74,0.15);
}
[data-theme="dark"] .mock-stat[data-color="info"] .mock-stat-icon {
    background: rgba(2,132,199,0.15);
}
[data-theme="dark"] .mock-stat[data-color="warning"] .mock-stat-icon {
    background: rgba(202,138,4,0.15);
}
[data-theme="dark"] .mock-card {
    background: #1a1a1a;
    border-color: #2a2a2a;
}
[data-theme="dark"] .mock-card-header {
    border-bottom-color: #2a2a2a;
}
[data-theme="dark"] .mock-cwv-metric {
    border-right-color: #2a2a2a;
}
[data-theme="dark"] .mock-input {
    background: #1a1a1a;
    color: #a3a3a3;
    border-color: #2a2a2a;
}
[data-theme="dark"] .mock-input:focus {
    border-color: #3a3a3a;
}
[data-theme="dark"] .mock-shimmer {
    background: #1a1a1a;
}
[data-theme="dark"] .mock-shimmer::after {
    background: linear-gradient(90deg, transparent, rgba(255,255,255,0.03), transparent);
}
[data-theme="dark"] .mock-code-bg {
    background: #111118;
}
[data-theme="dark"] .mock-code-lang {
    background: #1a1a1a;
    color: #a3a3a3;
}
[data-theme="dark"] .mock-badge-blue {
    background: rgba(59,130,246,0.15);
}
[data-theme="dark"] .mock-backup-success {
    background: rgba(12,206,107,0.15);
}
[data-theme="dark"] .mock-backup-scheduled {
    background: rgba(74,144,217,0.15);
}
[data-theme="dark"] .mock-tag-green {
    background: rgba(22,163,74,0.15);
}
[data-theme="dark"] .mock-breadcrumb {
    background: #111118;
    color: #a3a3a3;
}
[data-theme="dark"] .mock-analytics-tabs {
    background: #1a1a22;
}
[data-theme="dark"] .mock-analytics-tab {
    color: #6b7280;
}
[data-theme="dark"] .mock-analytics-tab--active {
    background: #2a2a35;
    color: #e5e7eb;
    box-shadow: 0 1px 3px rgba(0,0,0,0.3);
}
[data-theme="dark"] .mock-analytics-kpi-val:not(.mock-analytics-kpi-up) {
    color: #e5e5e5;
}
[data-theme="dark"] .mock-backup-row {
    border-bottom-color: #1f1f28;
}
[data-theme="dark"] .mock-badge-green {
    background: rgba(22,163,74,0.18);
    color: #4ade80;
}
/* The light-mode gray badge keeps #737373 in dark mode too (see the
   unconditional .mock-badge-gray rule further down), which measures at
   roughly 3.5:1 against its own translucent fill over a dark card or
   content background, below the 4.5:1 needed for text this size. Reuse
   --text-secondary's value, already the file's go-to legible gray on dark
   backgrounds (mock-input, mock-breadcrumb, mock-code-lang below). */
[data-theme="dark"] .mock-badge-gray {
    background: rgba(163,163,163,0.16);
    color: #a3a3a3;
}
[data-theme="dark"] .mock-ai-prompt {
    background: #1a1a22;
    color: #a3a3a3;
}
[data-theme="dark"] .mock-ai-line,
[data-theme="dark"] .mock-browser.animated .mock-ai-line {
    background: linear-gradient(90deg, #1f1f28 25%, #2a2a35 50%, #1f1f28 75%);
    background-size: 200% 100%;
}

[data-theme="dark"] .mock-view-head h4,
[data-theme="dark"] .mock-kpi-val,
[data-theme="dark"] .mock-row-title { color: #ededed; }
[data-theme="dark"] .mock-table td { color: #bdbdbd; border-top-color: #242424; }
[data-theme="dark"] .mock-row { border-top-color: #242424; }
[data-theme="dark"] .mock-kpi,
[data-theme="dark"] .mock-chip { background: #1a1a1a; border-color: #242424; }
[data-theme="dark"] .mock-bar { background: #242424; }
/* The neutral dot (no --ok/--warn) keeps #d4d4d4 in the light rule above, which
   reads as glaringly bright against a dark card. Mute it to the same subtle
   tone the file already uses for dark-theme hover borders, so it recedes the
   way it does in light mode. The :not() exclusions matter: an unqualified
   `[data-theme="dark"] .mock-dot` has higher specificity (0,2,0) than
   `.mock-dot--ok`/`--warn` (0,1,0) and would win over them regardless of
   source order, silently muting the ok/warn colours it is explicitly meant
   to leave alone. */
[data-theme="dark"] .mock-dot:not(.mock-dot--ok):not(.mock-dot--warn) { background: #3a3a3a; }

@media (max-width: 1024px) {
    .mock-stats { grid-template-columns: repeat(2, 1fr); }
    .mock-grid { grid-template-columns: 1fr; }
}

@media (max-width: 768px) {
    .panel-showcase { padding: var(--sp-4) 0 var(--sp-8); }
    .mock-browser { border-radius: var(--r-lg); transform: none; }
    .mock-browser:hover { transform: none; }
    .mock-stats { grid-template-columns: repeat(2, 1fr); gap: 6px; }
    .mock-stat { padding: 8px; gap: 6px; }
    .mock-stat-icon { width: 26px; height: 26px; }
    .mock-stat-icon svg { width: 12px; height: 12px; }
    .mock-stat-value { font-size: 14px; }
    .mock-stat-label { font-size: 9px; }
    .mock-grid { grid-template-columns: 1fr; }
    .mock-content { padding: 10px 12px; }
    .mock-header { padding: 8px 12px; }
    .mock-search { min-width: 120px; }

    /* The sidebar becomes a horizontal chip strip above the content. No height
       here: the grid stack sizes the frame, and on mobile the cards stack into
       one column, so any fixed height would crop the taller views. */
    .mock-panel {
        flex-direction: column;
        min-height: 0;
    }
    .mock-sidebar {
        width: 100%;
        padding: 8px 0;
        flex-direction: row;
        align-items: center;
    }
    .mock-logo { display: none; }
    .mock-nav {
        flex-direction: row;
        gap: 6px;
        padding: 0 10px;
        overflow-x: auto;
        overflow-y: hidden;
        scrollbar-width: none;
        -webkit-overflow-scrolling: touch;
    }
    .mock-nav::-webkit-scrollbar { display: none; }
    .mock-nav-item {
        width: auto;
        flex: 0 0 auto;
        height: 28px;
        padding: 0 11px;
        border-radius: 999px;
        background: rgba(255,255,255,0.06);
    }
    .mock-nav-item.active::before { display: none; }

    /* Table-shaped views (Licences, and more to come) are wider than the
       375px frame. The frame's overflow:hidden (needed for its rounded
       corners, see .mock-browser) would otherwise silently crop the
       rightmost column with no indication. Making the table itself the
       horizontally scrollable box keeps every column reachable, centrally,
       with no per-view wrapper markup needed.
       Scoped to this media query on purpose: the unconditional `.mock-table`
       rule in the shared vocabulary section below (width/border-collapse)
       sets neither `display` nor `overflow-x`, so it cannot re-win these two
       properties back regardless of where it sits in source order. */
    .mock-table {
        display: block;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }
}

/* View stack: all sections occupy the same grid cell and cross-fade.
   Every view stays a grid participant (never display:none), so the grid
   cell always sizes to the tallest view and the frame cannot clip. */
.mock-views {
    flex: 1;
    display: grid;
}
.mock-view {
    grid-area: 1 / 1;
    min-width: 0;
    opacity: 0;
    visibility: hidden;
    /* Delay the visibility flip until the fade finishes, so the outgoing
       view stays painted while it fades out. */
    transition: opacity 0.2s ease, visibility 0s linear 0.2s;
}
.mock-view.is-active {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.2s ease, visibility 0s;
}

/* Auto-tour progress sweep along the bottom of the active item. */
.mock-nav-progress {
    position: absolute;
    left: 0;
    bottom: 0;
    height: 2px;
    width: 0;
    background: #e23744;
    border-radius: 2px;
}
.mock-nav-progress.is-running {
    animation: mockTourSweep var(--mock-tour-duration, 4500ms) linear forwards;
}
@keyframes mockTourSweep {
    from { width: 0; }
    to   { width: 100%; }
}

/* Same grid stack as the views: every caption stays in the cell so it sizes to
   the tallest one and the strip never changes height as sections swap. No
   min-height magic number, and nothing here may use display:none. */
.mock-captions {
    display: grid;
    max-width: 980px;
    margin: 22px auto 0;
}
.mock-caption {
    grid-area: 1 / 1;
    text-align: center;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.2s ease, visibility 0s linear 0.2s;
}
.mock-caption.is-active {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.2s ease, visibility 0s;
}
.mock-caption-title {
    font-size: var(--text-xl);
    font-weight: 800;
    letter-spacing: -0.02em;
    margin: 0 0 6px;
}
.mock-caption-text {
    color: var(--text-muted);
    margin: 0 0 14px;
}
.mock-caption-chips {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
    list-style: none;
    margin: 0 0 14px;
    padding: 0;
}
.mock-caption-chips li {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 12px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--bg);
    font-size: var(--text-sm);
    font-weight: 600;
}
.mock-caption-chips svg { color: #16a34a; flex-shrink: 0; }
.mock-caption-link {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-weight: 700;
    color: var(--red);
    text-decoration: none;
}
.mock-caption-link:hover { text-decoration: underline; }

/* ── Shared vocabulary for the section views ── */
/* No padding rule here on purpose: every view root also carries .mock-content,
   which already owns the correct padding (including the @media(max-width:768px)
   mobile override). A same-specificity .mock-view padding rule placed after
   that media query in source order would win the tie and silently defeat the
   mobile override — see the fix-round-1 report for how that regression was
   found and reverted. */

.mock-view-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 10px;
}
.mock-view-head h4 {
    margin: 0;
    font-size: 12px;
    font-weight: 800;
    letter-spacing: -0.01em;
    color: #171717;
}

.mock-table { width: 100%; border-collapse: collapse; }
.mock-table th {
    text-align: left;
    font-size: 8px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #a3a3a3;
    padding: 0 8px 6px 0;
}
.mock-table td {
    font-size: 10px;
    font-weight: 600;
    color: #404040;
    padding: 7px 8px 7px 0;
    border-top: 1px solid #f0f0f0;
    white-space: nowrap;
}
.mock-table td:last-child, .mock-table th:last-child { padding-right: 0; }

/* No new .mock-badge base rule here: .mock-badge already exists (see the
   "Mock table" section above) and is live on the dashboard's backup badges.
   A second same-specificity base rule placed after it would win the cascade
   tie and silently restyle those shipped badges. .mock-badge-green and
   .mock-badge-blue already exist too (see "Mock table" and "Mock backup card"
   above); these two are the only genuinely new colour modifiers, named to
   match that existing single-dash convention instead of introducing a
   parallel one. */
.mock-badge-amber { background: rgba(217,119,6,0.12); color: #d97706; }
.mock-badge-gray  { background: rgba(115,115,115,0.12); color: #737373; }

.mock-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 7px;
    border: 1px solid #ececec;
    border-radius: 6px;
    background: #fff;
    font-size: 8px;
    font-weight: 700;
    color: #737373;
}

.mock-bar {
    display: block;
    height: 4px;
    border-radius: 999px;
    background: #f0f0f0;
    overflow: hidden;
}
.mock-bar-fill {
    display: block;
    height: 100%;
    border-radius: 999px;
    background: #e23744;
}

.mock-kpis { display: flex; gap: 10px; margin-bottom: 10px; }
.mock-kpi {
    flex: 1;
    padding: 8px 10px;
    border: 1px solid #f0f0f0;
    border-radius: 8px;
    background: #fff;
}
.mock-kpi-val {
    display: block;
    font-size: 13px;
    font-weight: 800;
    letter-spacing: -0.02em;
    color: #171717;
    line-height: 1.2;
}
.mock-kpi-label {
    font-size: 8px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #a3a3a3;
}

.mock-dot {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #d4d4d4;
}
.mock-dot--ok   { background: #16a34a; box-shadow: 0 0 0 2px rgba(22,163,74,0.15); }
.mock-dot--warn { background: #d97706; box-shadow: 0 0 0 2px rgba(217,119,6,0.15); }

.mock-row {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 8px 0;
    border-top: 1px solid #f0f0f0;
}
.mock-row:first-child { border-top: none; }
.mock-row-main { flex: 1; min-width: 0; }
.mock-row-title {
    display: block;
    font-size: 10px;
    font-weight: 700;
    color: #262626;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.mock-row-meta { font-size: 8px; font-weight: 600; color: #a3a3a3; }

.mock-site-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
}
.mock-site-card {
    padding: 10px;
    border: 1px solid #f0f0f0;
    border-radius: 8px;
    background: #fff;
}
.mock-site-head { display: flex; align-items: center; gap: 6px; margin-bottom: 7px; }
.mock-site-chips { display: flex; gap: 5px; margin-bottom: 8px; }
[data-theme="dark"] .mock-site-card { background: #1a1a1a; border-color: #242424; }

.mock-check {
    display: inline-block;
    width: 11px;
    height: 11px;
    border: 1.5px solid #d4d4d4;
    border-radius: 3px;
    vertical-align: middle;
}
.mock-check--on {
    background: #e23744;
    border-color: #e23744;
    position: relative;
}
.mock-check--on::after {
    content: '';
    position: absolute;
    left: 3px;
    top: 0.5px;
    width: 3px;
    height: 6px;
    border: solid #fff;
    border-width: 0 1.5px 1.5px 0;
    transform: rotate(45deg);
}
.mock-mini-btn {
    padding: 4px 10px;
    border-radius: 6px;
    background: #e23744;
    color: #fff;
    font-size: 9px;
    font-weight: 700;
}
/* Static width doubles as the fallback under prefers-reduced-motion below,
   so the bar never collapses to zero when the width animation is disabled.
   64% matches the update's original static midpoint. */
.mock-bar-fill--live {
    width: 64%;
    animation: mockBarProgress 3.5s ease-in-out infinite, mockBarPulse 1.6s ease-in-out infinite;
}
/* Genuine progress, not just an opacity pulse: the width oscillates between
   roughly 12% and 92% so the bar reads as an update actually in flight. */
@keyframes mockBarProgress { 0%, 100% { width: 12%; } 50% { width: 92%; } }
@keyframes mockBarPulse { 0%,100% { opacity: 1; } 50% { opacity: 0.55; } }

/* Analytics view: top pages next to countries, side by side. */
.mock-split { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 768px) {
    .mock-split { grid-template-columns: 1fr; gap: 8px; }
}

/* Support view: a short conversation thread between the customer and staff. */
.mock-support-thread {
    display: flex;
    flex-direction: column;
    gap: 5px;
    padding: 8px 0 8px 14px;
    border-left: 2px solid #f0f0f0;
    margin: 0 0 4px 3px;
}
.mock-support-msg {
    align-self: flex-start;
    max-width: 78%;
    padding: 6px 9px;
    border-radius: 8px;
    background: #f5f5f5;
    font-size: 9px;
    font-weight: 600;
    color: #525252;
}
.mock-support-msg--staff {
    align-self: flex-end;
    background: rgba(226,55,68,0.1);
    color: #b02a34;
}
[data-theme="dark"] .mock-support-thread { border-left-color: #242424; }
[data-theme="dark"] .mock-support-msg { background: #1f1f1f; color: #bdbdbd; }
/* Same specificity as the [data-theme="dark"] .mock-support-msg rule above
   (both are one attribute selector + one class), so this must stay after it
   in source order to win for the staff bubble and keep the two-tone
   conversation readable in dark mode instead of collapsing to one color. */
[data-theme="dark"] .mock-support-msg--staff { background: rgba(226,55,68,0.18); color: #ff8a94; }

/* Sites view: a warning alert card spanning both grid columns (see the markup
   in view-sites.html.twig). Reuses the exact amber tokens .mock-badge-amber
   already established (color #d97706, background rgba(217,119,6,0.12)) so
   the new card reads as the same "warning" language already on screen. */
.mock-alert-card {
    grid-column: 1 / -1;
    display: flex;
    flex-direction: column;
    gap: 6px;
    border-color: rgba(217,119,6,0.35);
    background: rgba(217,119,6,0.08);
}
[data-theme="dark"] .mock-alert-card {
    background: rgba(217,119,6,0.14);
    border-color: rgba(217,119,6,0.45);
}
.mock-alert-head { display: flex; align-items: center; gap: 7px; }
.mock-alert-title { font-size: 10px; font-weight: 700; color: #d97706; }
/* justify-content: flex-end because the foot now holds only the action
   link (there used to be a meta line alongside it); keeps the link in the
   same bottom-right slot the card has always used. */
.mock-alert-foot { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }

/* Blinking alert indicator: an expanding, fading ring around a static dot.
   ~1.6s ease-out, a gentle pulse rather than a hard strobe (well under the
   3-flashes-per-second threshold). Its own class rather than overloading
   .mock-dot, which has no ring today and is reused (unringed) elsewhere. */
.mock-alert-dot {
    position: relative;
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #d97706;
    flex-shrink: 0;
}
.mock-alert-dot::after {
    content: '';
    position: absolute;
    inset: -3px;
    border-radius: 50%;
    border: 1.5px solid #d97706;
    animation: mockAlertPing 1.6s ease-out infinite;
}
@keyframes mockAlertPing {
    0%   { transform: scale(0.4); opacity: 0.8; }
    100% { transform: scale(2.4); opacity: 0; }
}

/* Analytics view: a small inline-SVG flag before each country name (see
   partials/mock/flag.html.twig). A faint border keeps the white bands
   (France, Mexico, Argentina) from vanishing on a white card, in both themes. */
.mock-row-flag { display: flex; align-items: center; gap: 6px; }
.mock-flag {
    display: block;
    flex-shrink: 0;
    border-radius: 1px;
    border: 1px solid rgba(0,0,0,0.14);
}
[data-theme="dark"] .mock-flag { border-color: rgba(255,255,255,0.22); }

/* Backup view: a restore-in-progress demo, driven by startBackupRestoreDemo()
   in panel-mock.js. Deliberately its own bordered card rather than another
   .mock-backup-row, so it reads as a distinct live operation next to the
   static list above it. Blue "active operation" hue, the same one
   .mock-backup-scheduled/.mock-badge-blue already use for "this is in
   flight", not the green reserved for a finished backup. The bar itself
   reuses .mock-bar/.mock-bar-fill as-is (see those rules above); no parallel
   bar vocabulary is introduced here. */
.mock-restore-panel {
    margin-top: 12px;
    padding: 10px 12px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    border: 1px solid rgba(74,144,217,0.3);
    border-radius: 8px;
    background: rgba(74,144,217,0.06);
}
[data-theme="dark"] .mock-restore-panel {
    border-color: rgba(59,130,246,0.35);
    background: rgba(59,130,246,0.1);
}
.mock-restore-head { display: flex; align-items: center; gap: 8px; }
.mock-restore-icon {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    background: #e8f0fe;
    color: #4a90d9;
}
.mock-restore-icon svg { width: 12px; height: 12px; }
/* Spins while a restore is in flight; disabled below under
   prefers-reduced-motion and frozen once the cycle reaches "done" (JS toggles
   .is-done on the panel, never removes the element, so the icon just stops
   turning instead of disappearing). */
.mock-restore-icon svg { animation: mockRestoreSpin 1.6s linear infinite; }
@keyframes mockRestoreSpin { to { transform: rotate(360deg); } }
[data-theme="dark"] .mock-restore-icon { background: rgba(59,130,246,0.15); }
.mock-restore-info { flex: 1; display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.mock-restore-name { font-size: 11px; font-weight: 600; color: var(--text); }
.mock-restore-meta { font-size: 9px; color: #999; }
.mock-restore-pct { font-size: 11px; font-weight: 700; color: #4a90d9; flex-shrink: 0; }
[data-theme="dark"] .mock-restore-pct { color: #93c5fd; }
.mock-restore-stage { font-size: 9px; font-weight: 600; color: #999; }
/* Completed state: the same green already used for a finished backup row
   (.mock-backup-success), so "done" reads consistently across both. Scoped
   under .is-done rather than a new modifier on .mock-bar-fill, so the shared
   bar vocabulary stays untouched. */
.mock-restore-panel.is-done .mock-restore-icon { background: #e6f9ee; color: #0cce6b; }
.mock-restore-panel.is-done .mock-restore-icon svg { animation: none; }
.mock-restore-panel.is-done .mock-restore-pct { color: #0cce6b; }
.mock-restore-panel.is-done .mock-bar-fill { background: #0cce6b; }
[data-theme="dark"] .mock-restore-panel.is-done .mock-restore-icon { background: rgba(12,206,107,0.15); }

/* Kept last on purpose, and the ONLY prefers-reduced-motion block in this
   file: a media query adds no specificity over an unconditional rule for the
   same selector, so with equal specificity the later source position wins.
   Placing this single block at the end of the file means these overrides
   always win against whatever precedes them, and stay correct even as new
   unconditional rules are appended above. Do not add a second block earlier
   in the file, even for a rule that logically belongs next to the
   unconditional rule it disables (e.g. .mock-bar-fill--live above) — merge
   any new reduced-motion override into this one instead. */
@media (prefers-reduced-motion: reduce) {
    .mock-browser.reveal, .mock-browser.reveal.visible { opacity: 1; transform: perspective(1200px) rotateX(0deg) scale(1); transition: none; }

    /* Sections still swap, they just swap instantly. Keep visibility switching
       with no delay so the outgoing view cannot linger over the incoming one. */
    .mock-view,
    .mock-view.is-active,
    .mock-caption,
    .mock-caption.is-active { transition: none; }
    .mock-nav-progress.is-running { animation: none; width: 0; }
    /* Explicit non-zero width: the bar reads as "an update in progress" even
       standing still, instead of collapsing to an empty track. */
    .mock-bar-fill--live { animation: none; width: 64%; }
    .mock-nav-item.active .mock-nav-icon { animation: none; transform: none; }

    /* Static fallback: a resting halo instead of an expanding ring, so the
       alert still reads as "active" rather than looking broken or empty. */
    .mock-alert-dot::after { animation: none; transform: scale(1); opacity: 0.5; }

    /* The restore demo's own JS never starts its interval under reduced
       motion, so the bar/percentage/stage label stay at the static 64%
       "restoring files" markup rendered server-side; only the icon's spin
       (an unconditional CSS animation, not JS-driven) needs disabling here. */
    .mock-restore-icon svg { animation: none; }
}
