diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 2b494f00aa6..000a7329776 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,19 +1,7 @@ - - - - - +## Description -## Description of what has changed - - - +## Problem Solved -## Issues addressed by pull request - - - - - - - +## Alternatives Considered + +## Related issue(s) \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 2039c128b43..0efffb5be97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Website Changelog +## Unreleased + +### Features + +* Add dark mode! + +### Bug Fixes + +* Improve search results for exact ATT&CK ID and numeric ID queries by prioritizing matching object pages and relevant references. +* Fix sidebar loading for HTTPS redirects. +* Settle the search index write when an IndexedDB write fails, instead of leaving the promise pending and the search spinner up. +* Disable the search controls and explain why when the search index cannot be built, instead of leaving the spinner running for as long as the page is open. + ## v5.0.0 (2026-08-06) * Release ATT&CK content version 19.2. diff --git a/attack-search/__tests__/indexed-db-wrapper.test.js b/attack-search/__tests__/indexed-db-wrapper.test.js index 51e4727b3fc..2b379212209 100644 --- a/attack-search/__tests__/indexed-db-wrapper.test.js +++ b/attack-search/__tests__/indexed-db-wrapper.test.js @@ -57,4 +57,29 @@ describe('IndexedDBWrapper', () => { const count = await contentDb.count(); expect(count).toEqual(data.length); }); + + // A failed write must settle the promise. Racing against a sentinel tells a + // rejection apart from a promise that never settles at all, which a plain + // rejects assertion cannot do: it would time out and look like a slow test. + const settle = (promise) => Promise.race([ + promise.then(() => 'resolved', (error) => `rejected:${error.message}`), + new Promise((resolve) => setTimeout(() => resolve('HUNG'), 1000)), + ]); + + test('Bulk put rejects when the underlying write fails', async () => { + jest.spyOn(contentDb.indexeddb[contentDb.tableName], 'bulkPut') + .mockRejectedValue(new Error('QuotaExceededError')); + + await expect(settle(contentDb.bulkPut(data))).resolves.toBe('rejected:QuotaExceededError'); + }); + + test('Bulk put rejects when a later chunk fails', async () => { + let calls = 0; + jest.spyOn(contentDb.indexeddb[contentDb.tableName], 'bulkPut') + .mockImplementation(() => (++calls === 2 + ? Promise.reject(new Error('DatabaseClosedError')) + : Promise.resolve())); + + await expect(settle(contentDb.bulkPut(data, 1))).resolves.toBe('rejected:DatabaseClosedError'); + }); }); diff --git a/attack-search/__tests__/search-events.test.js b/attack-search/__tests__/search-events.test.js index 86af5fc0e19..45d30e8c3c7 100644 --- a/attack-search/__tests__/search-events.test.js +++ b/attack-search/__tests__/search-events.test.js @@ -112,8 +112,90 @@ describe('search event bindings', () => { expect(mockJqueryApis['[data-search-filter-dropdown="core"]'].attr) .toHaveBeenCalledWith('aria-hidden', 'false'); }); + + test('a failed index build stops search from waiting for an index that never arrives', async () => { + await loadIndexWithAFailingColdStart(); + + const parsingIcon = mockJqueryApis['#search-parsing-icon']; + parsingIcon.show.mockClear(); + parsingIcon.hide.mockClear(); + + handlerForSelector('#search-input')({ target: { value: 'mimikatz' } }); + + // Before the fix `search` looped on the loaded flag, so it showed the parsing icon on + // its first pass and kept doing so every 100ms for as long as the page stayed open. + expect(parsingIcon.show).not.toHaveBeenCalled(); + expect(parsingIcon.hide).toHaveBeenCalled(); + }); + + test('a failed restore from the cache is not reported as a successful load', async () => { + const { cacheKey, deleteCachedDatabase } = await loadIndexWithAFailingWarmRestore(); + + // The catch used to set the loaded flag false and the finally set it straight back to + // true, so `search` went on to query an index that was never populated. + expect(mockJqueryApis['#search-input'].prop).toHaveBeenCalledWith('disabled', true); + expect(mockJqueryApis['#search-icon'].addClass).toHaveBeenCalledWith('error-icon'); + expect(global.localStorage.removeItem).toHaveBeenCalledWith(cacheKey); + expect(deleteCachedDatabase).toHaveBeenCalledTimes(1); + }); + + test('a failed index build puts the search controls into their unavailable state', async () => { + await loadIndexWithAFailingColdStart(); + + expect(mockJqueryApis['#search-input'].prop).toHaveBeenCalledWith('disabled', true); + expect(mockJqueryApis['#search-button'].prop).toHaveBeenCalledWith('disabled', true); + expect(mockJqueryApis['#search-icon'].removeClass).toHaveBeenCalledWith('search-icon'); + expect(mockJqueryApis['#search-icon'].addClass).toHaveBeenCalledWith('error-icon'); + expect(mockJqueryApis['#search-button'].prop) + .toHaveBeenCalledWith('title', expect.stringContaining('search index could not be built')); + }); }); +// Load the module on the cold-start path with the document fetch failing, and run the +// debouncer straight through so the input handler reaches `search` without a timer. +async function loadIndexWithAFailingColdStart() { + global.window = { indexedDB: {} }; + global.localStorage.getItem.mockReturnValue(null); + + jest.doMock('../src/search-loader.js', () => ({ + loadSearchDocuments: () => Promise.reject(new Error('documents unavailable')), + })); + jest.doMock('../src/debouncer.js', () => class { + debounce(callback) { + callback(); + } + }); + + require('../src/index'); + await new Promise(resolve => setImmediate(resolve)); +} + +// Load the module on the cached path, with restoring the index from IndexedDB failing. +async function loadIndexWithAFailingWarmRestore() { + const { searchCacheCompatibilityVersion, searchCacheSchemaVersion } = require('../src/settings'); + const version = `${searchCacheSchemaVersion}-${searchCacheCompatibilityVersion}`; + const cacheKey = `saved_uuid_search_schema_${version}`; + const deleteCachedDatabase = jest.fn(() => Promise.resolve()); + + global.window = { indexedDB: {} }; + global.localStorage.getItem.mockReturnValue(`${global.build_uuid}-search-${version}`); + + jest.doMock('../src/search-service.js', () => class { + constructor() { + this.db = { indexeddb: { delete: deleteCachedDatabase } }; + } + + initializeAsync() { + return Promise.reject(new Error('cached index is unreadable')); + } + }); + + require('../src/index'); + await new Promise(resolve => setImmediate(resolve)); + + return { cacheKey, deleteCachedDatabase }; +} + function eventsForSelector(selector) { return mockJqueryCalls .filter(call => call.selector === selector || call.delegatedSelector === selector) diff --git a/attack-search/__tests__/search-service.test.js b/attack-search/__tests__/search-service.test.js index 87beddcec06..5c5c1c91771 100644 --- a/attack-search/__tests__/search-service.test.js +++ b/attack-search/__tests__/search-service.test.js @@ -22,11 +22,17 @@ describe('SearchService', () => { }); beforeEach(() => { + global.base_url = '/'; searchService = new SearchService('search-service', null); + searchService.render_container = { + append: jest.fn(), + html: jest.fn(), + }; }); afterEach(async () => { searchService = null; + delete global.base_url; }); it('Access data from mock-index.json', () => { @@ -111,4 +117,117 @@ describe('SearchService', () => { }); }); + + test('Keeps only exact ATT&CK ID matches and references, with the object first', async () => { + const documents = { + 1: { + id: 1, + title: 'TA577, Group G1037', + path: '/groups/G1037/index.html', + content: 'A group with no reference to the queried technique.', + attackId: 'G1037', + }, + 2: { + id: 2, + title: 'Ingress Tool Transfer, Technique T1105 - Enterprise', + path: '/techniques/T1105/index.html', + content: 'The T1105 technique.', + attackId: 'T1105', + }, + 3: { + id: 3, + title: 'A valid reference', + path: '/resources/reference/index.html', + content: 'This page references T1105.', + }, + }; + searchService.attackIndex.search = jest.fn().mockResolvedValue([{ field: 'title', result: [1, 2, 3] }]); + searchService.resolveSearchResults = jest.fn(async positions => positions.map(position => documents[position])); + + await searchService.query('t1105'); + + expect(searchService.allSearchResults.map(result => result.id)).toEqual([2, 3]); + }); + + test('Treats a four-digit query as an exact ATT&CK ID suffix search', async () => { + const documents = { + 1: { + id: 1, + title: 'TA577, Group G1037', + path: '/groups/G1037/index.html', + content: 'A group with no reference to the queried technique.', + attackId: 'G1037', + }, + 2: { + id: 2, + title: 'Data from Local System, Technique T1005 - Enterprise', + path: '/techniques/T1005/index.html', + content: 'The T1005 technique.', + attackId: 'T1005', + }, + 3: { + id: 3, + title: 'Matching software, Software S1005', + path: '/software/S1005/index.html', + content: 'The S1005 software.', + attackId: 'S1005', + }, + 4: { + id: 4, + title: 'A valid reference', + path: '/resources/reference/index.html', + content: 'This page references T1005.', + }, + 5: { + id: 5, + title: 'Data from Local System: Archive Collected Data, Sub-technique T1005.001', + path: '/techniques/T1005/001/index.html', + content: 'The T1005.001 sub-technique.', + attackId: 'T1005.001', + }, + }; + searchService.attackIndex.search = jest.fn().mockResolvedValue([{ field: 'title', result: [1, 3, 4, 2, 5] }]); + searchService.resolveSearchResults = jest.fn(async positions => positions.map(position => documents[position])); + + await searchService.query('1005'); + + expect(searchService.allSearchResults.map(result => result.id)).toEqual([2, 5, 3, 4]); + }); + + test.each(['TA0001', '0001'])('Promotes a tactic page for ATT&CK ID query %s', async (query) => { + const documents = { + 1: { + id: 1, + title: 'A valid reference', + path: '/resources/reference/index.html', + content: 'This page references TA0001.', + }, + 2: { + id: 2, + title: 'Initial Access, Tactic TA0001 - Enterprise', + path: '/tactics/TA0001/index.html', + content: 'The TA0001 tactic.', + attackId: 'TA0001', + }, + }; + searchService.attackIndex.search = jest.fn().mockResolvedValue([{ field: 'title', result: [1, 2] }]); + searchService.resolveSearchResults = jest.fn(async positions => positions.map(position => documents[position])); + + await searchService.query(query); + + expect(searchService.allSearchResults.map(result => result.id)).toEqual([2, 1]); + }); + + test('Preserves result ordering for non-ID queries', async () => { + const documents = { + 1: { id: 1, title: 'First result', path: '/resources/faq/index.html', content: 'Resources' }, + 2: { id: 2, title: 'Second result', path: '/resources/attackcon/index.html', content: 'Resources' }, + }; + searchService.attackIndex.search = jest.fn().mockResolvedValue([{ field: 'title', result: [1, 2] }]); + searchService.resolveSearchResults = jest.fn(async positions => positions.map(position => documents[position])); + + await searchService.query('Resources'); + + expect(searchService.allSearchResults.map(result => result.id)).toEqual([1, 2]); + }); }); diff --git a/attack-search/__tests__/search-style.test.js b/attack-search/__tests__/search-style.test.js index 73a43d2718e..f5713431710 100644 --- a/attack-search/__tests__/search-style.test.js +++ b/attack-search/__tests__/search-style.test.js @@ -20,7 +20,7 @@ describe('search styles', () => { const badgeStyle = styles.match(/\.search-result-badge\s*\{(?[^}]+)\}/)?.groups?.body ?? ''; - expect(badgeStyle).toContain('color: white;'); + expect(badgeStyle).toContain('color: color-functions.on-color(active);'); expect(badgeStyle).toContain('font-size: 0.8rem;'); expect(styles).toContain('.search-result-badge-page-type'); diff --git a/attack-search/__tests__/theme.test.js b/attack-search/__tests__/theme.test.js new file mode 100644 index 00000000000..ea6325bf4d3 --- /dev/null +++ b/attack-search/__tests__/theme.test.js @@ -0,0 +1,403 @@ +const fs = require('fs'); +const path = require('path'); + +const themeModulePath = '../../attack-theme/static/scripts/theme.js'; + +describe('site theme', () => { + let theme; + + beforeEach(() => { + jest.resetModules(); + theme = require(themeModulePath); + }); + + test('uses System when no saved override exists', () => { + const storage = createStorage(); + const root = createRoot(); + + expect(theme.readStoredPreference(storage)).toBe('system'); + + theme.applyPreference(root, 'system'); + + expect(root.removeAttribute).toHaveBeenCalledWith('data-theme'); + expect(root.style.setProperty).not.toHaveBeenCalled(); + }); + + test.each(['light', 'dark'])('applies a saved %s override before controls initialize', preference => { + const storage = createStorage(preference); + const root = createRoot(); + + const storedPreference = theme.readStoredPreference(storage); + theme.applyPreference(root, storedPreference); + + expect(storedPreference).toBe(preference); + expect(root.setAttribute).toHaveBeenCalledWith('data-theme', preference); + // CSS owns color-scheme so the print stylesheet can force light controls. + expect(root.style.setProperty).not.toHaveBeenCalled(); + }); + + test('discards an invalid saved preference', () => { + const storage = createStorage('sepia'); + + expect(theme.readStoredPreference(storage)).toBe('system'); + expect(storage.removeItem).toHaveBeenCalledWith(theme.STORAGE_KEY); + }); + + test('adds a working archive switch to the existing banner without replacing its content', () => { + const fixture = createControllerFixture({ storedPreference: 'dark' }); + const banner = { appendChild: jest.fn(), textContent: 'Currently viewing ATT&CK v3.0' }; + fixture.document.querySelector = jest.fn(selector => ( + selector === '.version-banner' ? banner : null + )); + fixture.document.createElement = jest.fn(() => fixture.toggle); + fixture.document.readyState = 'complete'; + + theme.bootstrap({ ...fixture, archived: true }); + + expect(fixture.document.documentElement.setAttribute).toHaveBeenCalledWith('data-archive-theme', ''); + expect(banner.appendChild).toHaveBeenCalledWith(fixture.toggle); + expect(banner.textContent).toBe('Currently viewing ATT&CK v3.0'); + expect(fixture.toggle.setAttribute).toHaveBeenCalledWith('aria-label', 'Dark mode'); + expect(fixture.toggle.setAttribute).toHaveBeenCalledWith('aria-checked', 'true'); + fixture.toggle.click(); + expect(fixture.storage.setItem).toHaveBeenCalledWith(theme.STORAGE_KEY, 'light'); + expect(fixture.toggle.setAttribute).toHaveBeenCalledWith('aria-checked', 'false'); + }); + + test('toggles from the system light theme to a saved dark override', () => { + const fixture = createControllerFixture({ systemDark: false }); + const controller = theme.createThemeController(fixture); + + controller.init(); + fixture.toggle.click(); + + expect(fixture.document.documentElement.setAttribute).toHaveBeenLastCalledWith('data-theme', 'dark'); + expect(fixture.storage.setItem).toHaveBeenCalledWith(theme.STORAGE_KEY, 'dark'); + expect(fixture.toggle.setAttribute).toHaveBeenCalledWith('aria-label', 'Switch to light mode'); + expect(fixture.toggle.setAttribute).toHaveBeenCalledWith('aria-checked', 'true'); + }); + + test('toggles from the system dark theme to a saved light override', () => { + const fixture = createControllerFixture({ systemDark: true }); + const controller = theme.createThemeController(fixture); + + controller.init(); + fixture.toggle.click(); + + expect(fixture.document.documentElement.setAttribute).toHaveBeenLastCalledWith('data-theme', 'light'); + expect(fixture.storage.setItem).toHaveBeenCalledWith(theme.STORAGE_KEY, 'light'); + expect(fixture.toggle.setAttribute).toHaveBeenCalledWith('aria-label', 'Switch to dark mode'); + expect(fixture.toggle.setAttribute).toHaveBeenCalledWith('aria-checked', 'false'); + }); + + test.each([ + ['light', 'dark'], + ['dark', 'light'], + ])('toggles a saved %s override to %s', (storedPreference, expectedPreference) => { + const fixture = createControllerFixture({ storedPreference }); + const controller = theme.createThemeController(fixture); + + controller.init(); + fixture.toggle.click(); + + expect(fixture.document.documentElement.setAttribute).toHaveBeenLastCalledWith( + 'data-theme', + expectedPreference, + ); + expect(fixture.storage.setItem).toHaveBeenCalledWith(theme.STORAGE_KEY, expectedPreference); + }); + + test('handles toggle clicks before DOMContentLoaded without double toggling after initialization', () => { + const fixture = createControllerFixture({ storedPreference: 'dark' }); + fixture.document.readyState = 'loading'; + + theme.bootstrap(fixture); + + // The stored preference is still applied synchronously to prevent a light-theme flash. + expect(fixture.document.documentElement.setAttribute).toHaveBeenLastCalledWith('data-theme', 'dark'); + + fixture.document.dispatchClick({ closest: jest.fn(() => fixture.toggle) }); + + expect(fixture.storage.setItem).toHaveBeenLastCalledWith(theme.STORAGE_KEY, 'light'); + expect(fixture.toggle.setAttribute).toHaveBeenCalledWith('aria-checked', 'false'); + + fixture.document.dispatchDOMContentLoaded(); + fixture.toggle.click(); + + expect(fixture.storage.setItem).toHaveBeenLastCalledWith(theme.STORAGE_KEY, 'dark'); + expect(fixture.storage.setItem).toHaveBeenCalledTimes(2); + expect(fixture.toggle.setAttribute).toHaveBeenCalledWith('aria-checked', 'true'); + }); + + test('ignores delegated clicks outside the theme toggle', () => { + const fixture = createControllerFixture(); + const controller = theme.createThemeController(fixture); + + fixture.document.dispatchClick({ closest: jest.fn(() => null) }); + controller.init(); + + expect(fixture.storage.setItem).not.toHaveBeenCalled(); + }); + + test('continues applying a choice when storage access fails', () => { + const fixture = createControllerFixture(); + fixture.storage.setItem.mockImplementation(() => { + throw new Error('Storage disabled'); + }); + const controller = theme.createThemeController(fixture); + + expect(() => { + controller.init(); + fixture.toggle.click(); + }).not.toThrow(); + expect(fixture.document.documentElement.setAttribute).toHaveBeenLastCalledWith('data-theme', 'dark'); + }); + + test('updates the toggle when the system preference changes before an override', () => { + const fixture = createControllerFixture({ systemDark: false }); + const controller = theme.createThemeController(fixture); + + controller.init(); + expect(fixture.toggle.setAttribute).toHaveBeenCalledWith('aria-label', 'Switch to dark mode'); + + fixture.mediaQuery.matches = true; + fixture.mediaQuery.dispatchChange(); + + expect(fixture.toggle.setAttribute).toHaveBeenCalledWith('aria-label', 'Switch to light mode'); + }); + + test('ignores OS preference changes while an explicit override is active', () => { + const fixture = createControllerFixture({ storedPreference: 'light', systemDark: false }); + const controller = theme.createThemeController(fixture); + + controller.init(); + fixture.mediaQuery.matches = true; + fixture.mediaQuery.dispatchChange(); + + expect(fixture.toggle.setAttribute).toHaveBeenCalledWith('aria-label', 'Switch to dark mode'); + }); + + test('synchronizes a theme change from another tab without writing it back', () => { + const fixture = createControllerFixture({ storedPreference: 'light' }); + theme.createThemeController(fixture).init(); + + fixture.storage.getItem.mockReturnValue('dark'); + fixture.document.defaultView.dispatchStorage({ + key: theme.STORAGE_KEY, + storageArea: fixture.storage, + }); + + expect(fixture.document.documentElement.setAttribute).toHaveBeenLastCalledWith('data-theme', 'dark'); + expect(fixture.toggle.setAttribute).toHaveBeenCalledWith('aria-checked', 'true'); + expect(fixture.storage.setItem).not.toHaveBeenCalled(); + fixture.toggle.click(); + expect(fixture.storage.setItem).toHaveBeenCalledWith(theme.STORAGE_KEY, 'light'); + }); + + test.each([null, 'attack-website-theme'])('returns to the system theme after storage removal (%s)', key => { + const fixture = createControllerFixture({ storedPreference: 'dark', systemDark: false }); + theme.createThemeController(fixture).init(); + + fixture.storage.getItem.mockReturnValue(null); + fixture.document.defaultView.dispatchStorage({ key, storageArea: fixture.storage }); + + expect(fixture.document.documentElement.removeAttribute).toHaveBeenCalledWith('data-theme'); + expect(fixture.toggle.setAttribute).toHaveBeenCalledWith('aria-checked', 'false'); + fixture.mediaQuery.matches = true; + fixture.mediaQuery.dispatchChange(); + expect(fixture.toggle.setAttribute).toHaveBeenLastCalledWith('data-theme-effective', 'dark'); + }); + + test('ignores unrelated storage keys and storage areas', () => { + const fixture = createControllerFixture({ storedPreference: 'light' }); + theme.createThemeController(fixture).init(); + fixture.storage.getItem.mockClear(); + + fixture.document.defaultView.dispatchStorage({ key: 'other', storageArea: fixture.storage }); + fixture.document.defaultView.dispatchStorage({ key: theme.STORAGE_KEY, storageArea: createStorage('dark') }); + + expect(fixture.storage.getItem).not.toHaveBeenCalled(); + }); + + test('loads the early theme script before styles and renders one toggle before search', () => { + const template = fs.readFileSync( + path.join(__dirname, '../../attack-theme/templates/general/base-template.html'), + 'utf8', + ); + const navigation = fs.readFileSync( + path.join(__dirname, '../../attack-theme/templates/macros/navigation_menu.html'), + 'utf8', + ); + + expect(template).toContain(''); + expect(template.indexOf('/theme/scripts/theme.js')).toBeLessThan(template.indexOf('bootstrap.min.css')); + expect(navigation.match(/\bdata-theme-toggle(?=[\s>])/g)).toHaveLength(1); + expect(navigation.indexOf('id="theme-toggle"')).toBeLessThan(navigation.indexOf('id="search-button"')); + expect(navigation).toContain('role="switch"'); + expect(navigation).toContain('class="theme-toggle-track"'); + expect(navigation).toContain('class="theme-toggle-thumb"'); + expect(navigation).toContain('aria-checked="false"'); + expect(navigation).not.toContain('aria-pressed'); + expect(navigation).not.toContain('data-theme-option'); + expect(navigation).not.toContain('theme-menu'); + expect(navigation).not.toContain('dropdown-toggle" type="button" data-theme-toggle'); + }); + + test('uses theme-aware surfaces for the affected resource pages', () => { + const council = fs.readFileSync( + path.join(__dirname, '../../modules/resources/templates/attack-advisory-council-members.html'), + 'utf8', + ); + const dataTools = fs.readFileSync( + path.join(__dirname, '../../modules/resources/templates/attack-data-and-tools.html'), + 'utf8', + ); + const attackcon = fs.readFileSync( + path.join(__dirname, '../../modules/resources/templates/attackcon-overview.html'), + 'utf8', + ); + + expect(council).toContain('background: var(--attack-color-body-alternate);'); + expect(council).toContain('color: var(--attack-on-color-body);'); + expect(dataTools).toContain('class="tab-content card card-body p-3 attack-excel-files"'); + expect(dataTools).not.toContain('style="background: #f8f9fa;"'); + expect(attackcon).toContain('"ATT&CKcon 4.0", "ATT&CKcon 5.0", "ATT&CKcon 6.0", "ATT&CKcon 7.0"'); + expect(attackcon).toContain('attackcon-banner-image{% if con.title in light_banner_titles %} on-light{% endif %}'); + }); + + test('uses theme-aware home controls and announcement banner colors', () => { + const home = fs.readFileSync( + path.join(__dirname, '../../attack-theme/templates/general/attack-index.html'), + 'utf8', + ); + const colors = fs.readFileSync( + path.join(__dirname, '../../attack-style/themes/_palette.scss'), + 'utf8', + ); + + expect(home).toContain('fa-up-right-from-square external-link-icon'); + expect(home).toContain('dropdown-toggle-split random-page-toggle'); + expect(home).not.toContain('external-site-dark.jpeg'); + expect(home).not.toContain('style="color: #4f7cac; background-color: white;'); + expect(colors).toContain('--attack-color-banner: #e7f0f6;'); + expect(colors).toContain('--attack-color-banner: #263a49;'); + }); + + test('renders the matrix Navigator link with a theme-aware icon', () => { + const matrix = fs.readFileSync( + path.join(__dirname, '../../modules/matrices/templates/matrix.html'), + 'utf8', + ); + + expect(matrix).toContain('fa-up-right-from-square'); + expect(matrix).not.toContain('external-site-dark.jpeg'); + }); + + test('paints the initial toggle state from the root theme and suppresses the Bootstrap focus ring', () => { + const nav = fs.readFileSync( + path.join(__dirname, '../../attack-style/layout/_nav.scss'), + 'utf8', + ); + + expect(nav).toContain(':root[data-theme="dark"] &'); + expect(nav).toContain(':root:not([data-theme]) &'); + expect(nav).toMatch(/&:focus\s*\{\s*outline: 0;\s*box-shadow: none;/); + }); + + test('uses a warm metadata label color only in dark mode', () => { + const colors = fs.readFileSync( + path.join(__dirname, '../../attack-style/themes/_palette.scss'), + 'utf8', + ); + const layout = fs.readFileSync( + path.join(__dirname, '../../attack-style/layout/_layout.scss'), + 'utf8', + ); + + expect(colors).toContain('--attack-color-property-label: #1d2226;'); + expect(colors).toContain('--attack-color-property-label: #f2d2a4;'); + expect(layout).toMatch(/\.card-data \.card-title\s*\{\s*color: color-functions\.color\(property-label\);/); + }); +}); + +function createStorage(value = null) { + return { + getItem: jest.fn(() => value), + removeItem: jest.fn(), + setItem: jest.fn(), + }; +} + +function createRoot() { + return { + removeAttribute: jest.fn(), + setAttribute: jest.fn(), + style: { + removeProperty: jest.fn(), + setProperty: jest.fn(), + }, + }; +} + +function createElement() { + const listeners = {}; + let dispatchClick; + const element = { + classList: { toggle: jest.fn() }, + setAttribute: jest.fn(), + addEventListener: jest.fn((eventName, listener) => { + listeners[eventName] = listener; + }), + closest: jest.fn(selector => (selector === '[data-theme-toggle]' ? element : null)), + connectClickDispatcher: dispatcher => { + dispatchClick = dispatcher; + }, + click: () => { + const event = { preventDefault: jest.fn(), target: element }; + if (listeners.click) listeners.click(event); + if (dispatchClick) dispatchClick(event); + }, + }; + + return element; +} + +function createControllerFixture({ storedPreference = null, systemDark = false } = {}) { + const root = createRoot(); + const toggle = createElement(); + let changeListener; + let clickListener; + let domContentLoadedListener; + let storageListener; + const mediaQuery = { + matches: systemDark, + addEventListener: jest.fn((eventName, listener) => { + if (eventName === 'change') changeListener = listener; + }), + dispatchChange: () => changeListener({ matches: mediaQuery.matches }), + }; + const document = { + documentElement: root, + querySelector: jest.fn(selector => (selector === '[data-theme-toggle]' ? toggle : null)), + addEventListener: jest.fn((eventName, listener) => { + if (eventName === 'click') clickListener = listener; + if (eventName === 'DOMContentLoaded') domContentLoadedListener = listener; + }), + dispatchClick: target => clickListener({ preventDefault: jest.fn(), target }), + dispatchDOMContentLoaded: () => domContentLoadedListener(), + defaultView: { + addEventListener: jest.fn((eventName, listener) => { + if (eventName === 'storage') storageListener = listener; + }), + dispatchStorage: event => storageListener(event), + }, + }; + toggle.connectClickDispatcher(event => clickListener(event)); + + return { + document, + mediaQuery, + storage: createStorage(storedPreference), + toggle, + }; +} diff --git a/attack-search/src/index.js b/attack-search/src/index.js index d3aa49f3b30..54ad023b402 100644 --- a/attack-search/src/index.js +++ b/attack-search/src/index.js @@ -89,6 +89,40 @@ const closeSearch = function () { // Variable to check if search service is loaded let searchServiceIsLoaded = false; +// Set once the index cannot be built at all. Without it `search` waits for a flag that is +// never going to flip and the parsing spinner runs for as long as the page is open. +let searchServiceUnavailable = false; + +// Put the search controls into their unavailable state and explain why on hover. +function markSearchUnavailable(reason) { + searchServiceUnavailable = true; + searchServiceIsLoaded = false; + searchInput.prop('disabled', true); + searchButton.prop('disabled', true); + searchIcon.removeClass('search-icon'); + searchIcon.addClass('error-icon'); + searchButton.prop('title', reason); +} + +// Remove a failed cached index so the next page load rebuilds it instead of retrying +// the same restore path. Cache cleanup is best-effort and must not mask the original +// initialization failure or prevent the unavailable UI state from being shown. +async function invalidateSearchCache() { + try { + localStorage.removeItem(searchCacheKey); + } catch (error) { + console.error('Failed to remove the search cache marker:', error); + } + + try { + await searchService.db.indexeddb.delete(); + } catch (error) { + console.error('Failed to delete the cached search index:', error); + } +} + +const SEARCH_INDEX_FAILED_MESSAGE = 'The search index could not be built. Reload the page to try again.'; + // Initialize the search service async function initializeSearchService() { console.debug('Initializing search service...'); @@ -111,12 +145,13 @@ async function initializeSearchService() { await searchService.initializeAsync(null); // Passing null will instruct the search service to attempt // restoring itself from the IndexedDB console.debug('SearchService is initialized.'); + searchServiceIsLoaded = true; } catch (error) { console.error('Failed to initialize SearchService:', error); - searchServiceIsLoaded = false; + markSearchUnavailable(SEARCH_INDEX_FAILED_MESSAGE); + await invalidateSearchCache(); } finally { searchParsingIcon.hide(); - searchServiceIsLoaded = true; } } else { @@ -139,18 +174,14 @@ async function initializeSearchService() { .catch(error => { console.error('Failed to initialize SearchService:', error); searchParsingIcon.hide(); - searchServiceIsLoaded = false; + markSearchUnavailable(SEARCH_INDEX_FAILED_MESSAGE); }); } } else { // Disable the search button and display an error icon with a hover effect that displays a message/explanation console.error('Search is only available in browsers that support IndexedDB. Please try using Firefox, Chrome, Safari, or another browser that supports IndexedDB.'); - searchInput.prop('disabled', true); - searchButton.prop('disabled', true); - searchIcon.removeClass('search-icon'); - searchIcon.addClass('error-icon'); - searchButton.prop('title', 'To use the search feature, please make sure your browser supports IndexedDB. If not, consider upgrading your browser or switching to a supported browser such as Firefox, Chrome, or Safari.') + markSearchUnavailable('To use the search feature, please make sure your browser supports IndexedDB. If not, consider upgrading your browser or switching to a supported browser such as Firefox, Chrome, or Safari.'); } } @@ -158,13 +189,18 @@ async function initializeSearchService() { const search = async function (query) { console.debug(`search -> Received search query: ${query}`); - // Wait until the search service is loaded - while (!searchServiceIsLoaded) { + // Wait until the search service is loaded, or until we know it never will be. + while (!searchServiceIsLoaded && !searchServiceUnavailable) { console.debug('search -> search index is not loaded...'); searchParsingIcon.show(); await new Promise(resolve => setTimeout(resolve, 100)); } + if (searchServiceUnavailable) { + searchParsingIcon.hide(); + return; + } + console.debug(`Executing search: ${query}`); await searchService.query(query); searchParsingIcon.hide(); diff --git a/attack-search/src/indexed-db-wrapper.js b/attack-search/src/indexed-db-wrapper.js index df24f3ff08b..9ac1400b02a 100644 --- a/attack-search/src/indexed-db-wrapper.js +++ b/attack-search/src/indexed-db-wrapper.js @@ -23,7 +23,7 @@ class TableWrapper { */ async bulkPut(data, chunkSize = 100) { - return new Promise(async (resolve) => { + return new Promise((resolve, reject) => { /** * Schedules work using requestIdleCallback if supported, or setTimeout as a fallback. * @param {Function} callback - The function to be executed when the browser is idle or after the specified delay. @@ -44,23 +44,28 @@ class TableWrapper { * @param {number} start - The index of the first item in the data array to be included in the current chunk. */ const putChunk = async (start) => { - // If all data has been processed, resolve the promise - if (start >= data.length) { - resolve(); - return; + try { + // If all data has been processed, resolve the promise + if (start >= data.length) { + resolve(); + return; + } + + // Determine the end index for the current chunk + const end = Math.min(start + chunkSize, data.length); + + // Extract the chunk from the data array + const chunk = data.slice(start, end); + + // Insert the chunk into the IndexedDB table + await this.indexeddb[this.tableName].bulkPut(chunk); + + // Schedule the next chunk to be processed + scheduleWork(() => putChunk(end)); + } catch (error) { + // Nothing else settles this promise, so callers would wait forever. + reject(error); } - - // Determine the end index for the current chunk - const end = Math.min(start + chunkSize, data.length); - - // Extract the chunk from the data array - const chunk = data.slice(start, end); - - // Insert the chunk into the IndexedDB table - await this.indexeddb[this.tableName].bulkPut(chunk); - - // Schedule the next chunk to be processed - scheduleWork(() => putChunk(end)); }; // Start processing the data array by inserting the first chunk diff --git a/attack-search/src/search-service.js b/attack-search/src/search-service.js index 5a38f6ecfe5..bd03b763296 100644 --- a/attack-search/src/search-service.js +++ b/attack-search/src/search-service.js @@ -313,10 +313,84 @@ module.exports = class SearchService { * ] */ - this.allSearchResults = await this.#setSearchResults(results); + this.allSearchResults = this.#filterAndPromoteExactAttackIdMatches(await this.#setSearchResults(results)); this.#renderFilteredSearchResults(); } + /** + * Limits ATT&CK ID searches to matching objects, their sub-techniques, and genuine references. + * Non-ID and multi-token queries retain FlexSearch's existing ordering. + * + * @private + * @param {Array} documents - Search results in their existing relevance order. + * @returns {Array} Exact ATT&CK ID results, with the matching object detail page first when applicable. + */ + #filterAndPromoteExactAttackIdMatches(documents) { + const query = this.currentQuery.clean; + const isExactAttackId = /^[A-Z]+\d+(?:\.\d+)?$/i.test(query); + const isNumericIdSuffix = /^\d{4}$/.test(query); + // If user queries for normal text and not attack ids, normal search takes place + if (!isExactAttackId && !isNumericIdSuffix) return documents; + + const normalizedQuery = query.toUpperCase(); + + // Collect the IDs stored on object-detail search records. Resource and reference pages have no attackId. + const candidateAttackIds = documents + .map(document => document.attackId?.toUpperCase()) + .filter(Boolean); + + let directAttackIds; + if (isExactAttackId) { + // A complete query such as T1005 refers directly to that one ID. + directAttackIds = [normalizedQuery]; + } else { + // A numeric query such as 1005 may match T1005, S1005, or another complete ATT&CK ID. + const numericSuffixPattern = new RegExp(`^[A-Z]+${normalizedQuery}$`); + directAttackIds = [...new Set(candidateAttackIds.filter(attackId => numericSuffixPattern.test(attackId)))]; + } + + // Include sub-techniques of a matching parent technique, such as T1005.001 for a T1005 query. + const subTechniqueIds = candidateAttackIds.filter((attackId) => directAttackIds.some((directAttackId) => ( + directAttackId.startsWith('T') + && !directAttackId.includes('.') + && attackId.startsWith(`${directAttackId}.`) + ))); + const matchingAttackIds = [...new Set([...directAttackIds, ...subTechniqueIds])]; + if (matchingAttackIds.length === 0) return []; + + // Put parent techniques first, then their sub-techniques, followed by other matching ATT&CK object types. + const exactMatches = documents.filter(document => matchingAttackIds.includes(document.attackId?.toUpperCase())); + exactMatches.sort((first, second) => { + const firstIsTechnique = first.attackId.startsWith('T'); + const secondIsTechnique = second.attackId.startsWith('T'); + if (firstIsTechnique !== secondIsTechnique) return firstIsTechnique ? -1 : 1; + + const firstIsSubTechnique = first.attackId.includes('.'); + const secondIsSubTechnique = second.attackId.includes('.'); + if (firstIsSubTechnique !== secondIsSubTechnique) return firstIsSubTechnique ? 1 : -1; + + return 0; + }); + + // Escape dots in sub-technique IDs before making one expression that matches only whole IDs. + const escapedIds = matchingAttackIds.map(attackId => attackId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + // A trailing period is valid sentence punctuation, unless it begins a sub-technique suffix such as .001. + const exactIdInText = new RegExp( + `(^|[^A-Z0-9.])(?:${escapedIds.join('|')})(?=$|[^A-Z0-9.]|\\.(?!\\d))`, + 'i', + ); + + // Add pages that reference a matching ID, but do not add an object-detail page twice. + const referencedDocuments = documents.filter((document) => { + const title = document.title ?? ''; + const content = document.content ?? ''; + const referencesMatchingId = exactIdInText.test(title) || exactIdInText.test(content); + return referencesMatchingId && !exactMatches.includes(document); + }); + + return exactMatches.concat(referencedDocuments); + } + /** * Renders the search results on the web page based on the given search result page. * If the search query is empty, it will show the "Load More Results" button. diff --git a/attack-search/src/settings.js b/attack-search/src/settings.js index 14451cd250b..6218aace463 100644 --- a/attack-search/src/settings.js +++ b/attack-search/src/settings.js @@ -1,7 +1,7 @@ const baseURL = ''; // TODO migrate from base_url (generated via Pelican) const packageJson = require('../package.json'); -const searchCacheSchemaVersion = 3; +const searchCacheSchemaVersion = 4; const flexSearchVersion = packageJson.dependencies.flexsearch.replace(/^[^\d]*/, ''); const searchCacheCompatibilityVersion = `flexsearch-${flexSearchVersion}`; diff --git a/attack-style/README.md b/attack-style/README.md index 201a7c83507..edc7014b010 100644 --- a/attack-style/README.md +++ b/attack-style/README.md @@ -1,10 +1,11 @@ # ATT&CK Style ATT&CK Style is a JavaScript package that builds the CSS styles for the ATT&CK website. -The outputs are simply 2 CSS files: +The outputs are 3 CSS files: * `dist/style-attack.css` * `dist/style-user.css` +* `dist/style-archive.css` (preserved-site appearance compatibility) These files are then copied into `/attack-theme/static/`. Currently this is done manually - no automation. @@ -47,7 +48,7 @@ To set up the ATT&CK Style package, follow these steps: 2. **Copy CSS Files**: - Copy both `dist/style-attack.css` and `dist/style-user.css` to `/attack-theme/static/`. + Copy `dist/style-attack.css`, `dist/style-user.css`, and `dist/style-archive.css` to `/attack-theme/static/`. ```bash npm run copy diff --git a/attack-style/abstracts/README.md b/attack-style/abstracts/README.md index c6327328313..c354b0af464 100644 --- a/attack-style/abstracts/README.md +++ b/attack-style/abstracts/README.md @@ -8,14 +8,14 @@ Files in this folder should not emit large blocks of CSS on their own unless the | File | Purpose | | --- | --- | | `_variables.scss` | Defines brand and user color maps plus the semantic `$colors` map used across the site. | -| `_color-functions.scss` | Provides accessors and derived color helpers for entries in `$colors`. | +| `_color-functions.scss` | Provides accessors for runtime semantic color tokens. | | `_utilities.scss` | Provides small reusable mixins and unit helpers. | | `_font-faces.scss` | Defines shared font-face declarations. | ## Color Model -`_variables.scss` keeps raw brand values separate from semantic color names. -Most styles should use semantic keys from `$colors`, such as `primary`, `secondary`, `footer`, `active`, `body`, `link`, `matrix-header`, `search-highlight`, and `deemphasis`. +`_variables.scss` keeps raw brand values separate from semantic color names. The theme palette in `themes/_palette.scss` turns those values into CSS custom properties so the appearance can change without loading another stylesheet. +Most styles should use semantic names such as `primary`, `secondary`, `footer`, `active`, `body`, `link`, `matrix-header`, `search-highlight`, and `deemphasis`. Each color entry may contain: @@ -28,18 +28,17 @@ Some entries omit `on-color` when they are not meant to contain inner text. ## Helper Functions -Use the functions in `_color-functions.scss` instead of reading `$colors` directly from component or layout files: +Use the functions in `_color-functions.scss` instead of reading `$colors` directly from component or layout files. Each helper returns the appropriate runtime CSS custom property: | Function | Use | | --- | --- | | `color($name)` | Reads the base color for a semantic color name. | -| `on-color($name)` | Reads the readable text color for a semantic color name. | -| `color-alternate($name, $contrast: 1)` | Computes a nearby alternate shade for patterning or subtle contrast. | -| `on-color-emphasis($name)` | Computes a stronger foreground color against a semantic background. | -| `on-color-deemphasis($name)` | Computes a quieter foreground color against a semantic background. | -| `border-color($name)` | Computes a border color for a semantic background. | -| `background-color($name)` | Computes a subtle derived background shade. | -| `escape-color($color)` | Escapes a concrete color for use inside inline SVG data URLs. | +| `on-color($name)` | Reads the readable foreground for a semantic color name. | +| `color-alternate($name, $contrast: 1)` | Reads an explicit alternate surface token. Supported contrast levels are `0.8`, `1`, `1.5`, `2`, and `3`. | +| `on-color-emphasis($name)` | Reads a stronger foreground token. | +| `on-color-deemphasis($name)` | Reads a quieter foreground token. | +| `border-color($name)` | Reads a border token. | +| `background-color($name)` | Reads a related background token. | ## Utility Mixins And Functions diff --git a/attack-style/abstracts/_color-functions.scss b/attack-style/abstracts/_color-functions.scss index 4407f1f3d8b..69b0b1dc9c7 100644 --- a/attack-style/abstracts/_color-functions.scss +++ b/attack-style/abstracts/_color-functions.scss @@ -1,55 +1,55 @@ -@use "sass:color"; -@use "sass:map"; -@use "sass:string"; -@use "variables"; - -// accessor helper for $colors. Gets the color of the named pair +// Accessor helper for semantic runtime colors. @function color($name) { - @return map.get(map.get(variables.$colors, $name), "color"); + @return var(--attack-color-#{$name}); } -// given a color name, get an alternate version of the color, for patterning -// if the base color is dark, the alternate will be slightly lighter. -// if the base color is light, the alternate will be slightly darker. -// contrast, an optional argument, multiplies to create a more distint or similar color. >1 is more distant, <1 is more similar. +// Get an explicit alternate surface token. Supported contrast values match the +// existing call sites and avoid requiring runtime color-mix support. @function color-alternate($name, $contrast: 1) { - @return color.mix(color.invert(color($name)), color($name), $weight: $contrast * 5%); + @if $contrast == 0.8 { + @return var(--attack-color-#{$name}-alternate-subtle); + } + + @if $contrast == 1 { + @return var(--attack-color-#{$name}-alternate); + } + + @if $contrast == 1.5 { + @return var(--attack-color-#{$name}-alternate-medium); + } + + @if $contrast == 2 { + @return var(--attack-color-#{$name}-alternate-strong); + } + + @if $contrast == 3 { + @return var(--attack-color-#{$name}-alternate-strongest); + } + + @error "Unsupported alternate color contrast: #{$contrast}"; } -/// accessor helper for $colors. Gets the on-color of the named pair +/// Accessor helper for readable text on a semantic color. @function on-color($name) { - @return map.get(map.get(variables.$colors, $name), "on-color"); + @return var(--attack-on-color-#{$name}); } -/// given a color-name, get an emphasized version of the on-color. -/// The emphasized on-color is less like the background color. +/// Get an emphasized foreground token for a semantic color. @function on-color-emphasis($name) { - @return color.mix(color.invert(color($name)), on-color($name)); + @return var(--attack-on-color-#{$name}-emphasis); } -// given a color-name, get an deemphasized version of the on-color. -// The deemphasized on-color is more like the background color. +// Get a deemphasized foreground token for a semantic color. @function on-color-deemphasis($name) { - @return color.mix(color($name), on-color($name), 25%); + @return var(--attack-on-color-#{$name}-deemphasis); } -// given a color name, compute a border color for the color +// Get an explicit border token for a semantic color. @function border-color($name) { - @return color.mix(color.invert(color($name)), color($name), 12.5%); - - // @return rgba(invert(color($name)), 0.125); + @return var(--attack-border-color-#{$name}); } -// given a color name, compute a border color for the color +// Get an explicit hover/background token for a semantic color. @function background-color($name) { - @return color.mix(color.invert(color($name)), color($name), 12.5%); -} - -// escape the color. Note param is a color and not a color name: this is not an accessor to the color map above. -// replaces # with %23 in hex colors -// see https://codepen.io/gunnarbittersmann/pen/BoovjR for explanation of why we have to escape # for the background image -@function escape-color($color) { - $hex: color.ie-hex-str($color); - - @return "%23" + string.slice($string: #{$hex}, $start-at: 4); // skip #AA in #AARRGGBB + @return var(--attack-background-color-#{$name}); } diff --git a/attack-style/abstracts/_variables.scss b/attack-style/abstracts/_variables.scss index bdbc472efa0..9d387c8261b 100644 --- a/attack-style/abstracts/_variables.scss +++ b/attack-style/abstracts/_variables.scss @@ -27,25 +27,25 @@ $user-colors: ( /// $colors: ( primary: ( - color: if(config.$use-attack-theme, map.get($attack-colors, attack-orange), map.get($user-colors, user-gray)), + color: if(sass(config.$use-attack-theme): map.get($attack-colors, attack-orange); else: map.get($user-colors, user-gray)), on-color: white ), // used for header and some nav elements secondary: ( - color: if(config.$use-attack-theme, map.get($attack-colors, attack-blue), map.get($user-colors, user-gray)), + color: if(sass(config.$use-attack-theme): map.get($attack-colors, attack-blue); else: map.get($user-colors, user-gray)), on-color: white ), // used for some buttons footer: ( - color: if(config.$use-attack-theme, map.get($attack-colors, attack-footer), map.get($user-colors, user-gray)), + color: if(sass(config.$use-attack-theme): map.get($attack-colors, attack-footer); else: map.get($user-colors, user-gray)), on-color: #87deff ), // used for footer and some buttons active: ( - color: if(config.$use-attack-theme, map.get($attack-colors, attack-active), map.get($user-colors, user-gray)), + color: if(sass(config.$use-attack-theme): map.get($attack-colors, attack-active); else: map.get($user-colors, user-gray)), on-color: #eaeaea ), // used for active buttons and sidebar links @@ -59,7 +59,7 @@ $colors: ( // body: (color: rgb(50, 50, 50), on-color: #cdcdcd), link: ( - color: #4f7cac + color: #3f709e ), // hyperlinks matrix-header: @@ -75,6 +75,7 @@ $colors: ( on-color: black ), deemphasis: ( - color: #303435 + color: #686f75, + on-color: white ) ); diff --git a/attack-style/components/_matrix.scss b/attack-style/components/_matrix.scss index 0804423c173..a49c57de959 100644 --- a/attack-style/components/_matrix.scss +++ b/attack-style/components/_matrix.scss @@ -123,7 +123,7 @@ $sizeunit: 14px; &.count { font-size: $sizeunit - 1px; - border-bottom: 1px solid black; + border-bottom: 1px solid color-functions.border-color(body); padding-bottom: 5px; margin-bottom: 5px; } @@ -247,7 +247,7 @@ $sizeunit: 14px; &.count { font-size: $sizeunit - 1px; - border-bottom: 1px solid black; + border-bottom: 1px solid color-functions.border-color(body); padding-bottom: 5px; margin-bottom: 5px; } @@ -365,9 +365,9 @@ $sizeunit: 14px; // the menu when the user is clicking. Instead use the // bootstrap hover style. &:active { - color: #16181b; + color: color-functions.on-color(body); text-decoration: none; - background-color: #f8f9fa; + background-color: color-functions.color-alternate(body, 0.8); } } } diff --git a/attack-style/components/_search.scss b/attack-style/components/_search.scss index fbcdaba0216..24b6c9a869c 100644 --- a/attack-style/components/_search.scss +++ b/attack-style/components/_search.scss @@ -232,7 +232,7 @@ padding: 2px 8px; border: 1px solid; border-radius: 4px; - color: white; + color: color-functions.on-color(active); font-size: 0.8rem; font-weight: 700; } @@ -245,6 +245,7 @@ .search-result-badge-domain { border-color: color-functions.color(deemphasis); background: color-functions.color(deemphasis); + color: color-functions.on-color(deemphasis); } .search-no-results .preview { diff --git a/attack-style/layout/_footer.scss b/attack-style/layout/_footer.scss index cd7f7f1cbb4..095dfd31644 100644 --- a/attack-style/layout/_footer.scss +++ b/attack-style/layout/_footer.scss @@ -1,4 +1,3 @@ -@use "sass:color"; @use "../abstracts/color-functions"; @use "../abstracts/utilities"; @@ -53,8 +52,7 @@ color: color-functions.on-color(footer); &:hover { - // add some link color to this so that it resembles a link, but is still visible if normal link color is not visible on footer color - color: color.mix(color-functions.on-color(footer), color-functions.color(link)); + color: color-functions.color(footer-link-hover); } } } diff --git a/attack-style/layout/_layout.scss b/attack-style/layout/_layout.scss index 87fbda5516b..d7bbc97be32 100644 --- a/attack-style/layout/_layout.scss +++ b/attack-style/layout/_layout.scss @@ -1,4 +1,3 @@ -@use "sass:color"; @use "sass:math"; @use "../abstracts/color-functions"; @use "../abstracts/utilities"; @@ -65,6 +64,10 @@ strong { a { color: color-functions.color(link); + &:hover { + color: color-functions.color(link-hover); + } + .anchor::before { content: ""; display: block; @@ -133,6 +136,27 @@ a { tr + tr { border-top: 1px solid color-functions.border-color(body); } + + .external-link-icon { + margin-left: utilities.to-rem(2); + font-size: utilities.to-rem(13); + } + + .random-page-toggle { + margin-left: utilities.to-rem(6); + padding: 0 utilities.to-rem(6); + border: 1px solid color-functions.color(active); + color: color-functions.color(link); + background: color-functions.color(body); + + &:hover, + &:focus, + &[aria-expanded="true"] { + border-color: color-functions.color(secondary); + color: color-functions.on-color(secondary); + background: color-functions.color(secondary); + } + } } .row-main-page { @@ -151,7 +175,7 @@ a { // p for home page .p-line { p { - border-top: 0.0625rem solid #1c2226; + border-top: 0.0625rem solid color-functions.border-color(body); } } @@ -187,7 +211,7 @@ a { @extend .website-button; border-color: color-functions.color(active); - color: #fff; + color: color-functions.on-color(active); background: color-functions.color(active); padding: 6px 16px; @@ -200,7 +224,7 @@ a { @extend .website-button; color: color-functions.color(active); - background: #fff; + background: color-functions.color(body); border-color: color-functions.color(active); padding: 6px 16px; @@ -218,15 +242,21 @@ a { padding-left: 8px; } -.slide-button:hover { +.slide-button:hover, +.slide-button:focus, +.slide-button:active, +.slide-button[aria-expanded="true"] { background: color-functions.color(secondary); border-color: color-functions.color(secondary); + color: color-functions.on-color(secondary); } -.slide-button-secondary:hover { - background: color-functions.on-color(active); +.slide-button-secondary:hover, +.slide-button-secondary:focus, +.slide-button-secondary:active { + background: color-functions.color(secondary); border-color: color-functions.color(secondary); - color: color-functions.color(secondary); + color: color-functions.on-color(secondary); } // used for data sources filter dropdown @@ -238,7 +268,7 @@ a { .dropdown-content { display: none; position: absolute; - background-color: color-functions.on-color(secondary); + background-color: color-functions.color(body); min-width: 160px; box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 20%); } @@ -264,7 +294,7 @@ a { // Extending placeholder 'button-style' is necessary to keep on-color white. Else, the on-color will change to the tag on-color when hovering the button. @extend %button-style; - background-color: color.scale(color-functions.color(secondary), $lightness: 5%); + background-color: color-functions.color(secondary-hover); background-image: none; } } @@ -313,7 +343,7 @@ a { } .active { - color: color-functions.color(primary); + color: color-functions.color(active); } } @@ -336,8 +366,8 @@ a { // table for techniques .table-techniques { thead tr { - background: #f2f2f2; - border-bottom: 2px solid #dee2e6; + background: color-functions.color-alternate(body); + border-bottom: 2px solid color-functions.border-color(body); } table { @@ -347,11 +377,11 @@ a { td { vertical-align: top; padding: 10px; - border: 1px solid #dfdfdf; + border: 1px solid color-functions.border-color(body); } tr:last-child { - border-bottom: 1px solid #dfdfdf; + border-bottom: 1px solid color-functions.border-color(body); } .sub.technique { @@ -369,11 +399,11 @@ a { } .sub.technique td:not(:nth-child(4)) { - color: #4f7cac; + color: color-functions.color(link); } .technique:not(.sub) td:not(:nth-child(3)) { - color: #4f7cac; + color: color-functions.color(link); } } @@ -386,11 +416,11 @@ a { td { vertical-align: top; padding: 10px; - border: 1px solid #dfdfdf; + border: 1px solid color-functions.border-color(body); } tr:last-child { - border-bottom: 1px solid #dfdfdf; + border-bottom: 1px solid color-functions.border-color(body); } .sub.technique { @@ -420,8 +450,8 @@ a { .techniques-used.background { thead tr { - background: #f2f2f2; - border-bottom: 2px solid #dee2e6; + background: color-functions.color-alternate(body); + border-bottom: 2px solid color-functions.border-color(body); } } @@ -436,18 +466,18 @@ a { } thead tr { - background: #f2f2f2; - border-bottom: 2px solid #dee2e6; + background: color-functions.color-alternate(body); + border-bottom: 2px solid color-functions.border-color(body); } td { vertical-align: top; padding: 10px; - border: 1px solid #dfdfdf; + border: 1px solid color-functions.border-color(body); } tr:last-child { - border-bottom: 1px solid #dfdfdf; + border-bottom: 1px solid color-functions.border-color(body); } .datacomponent.datasource { @@ -583,9 +613,18 @@ a { /* BANNER */ .banner-message { - padding: utilities.to-rem(5) 0; + padding: utilities.to-rem(7) utilities.to-rem(16); + border-top: 1px solid color-functions.border-color(banner); + border-bottom: 1px solid color-functions.border-color(banner); text-align: center; - background-color: color-functions.color-alternate(body, 2); + color: color-functions.on-color(banner); + background-color: color-functions.color(banner); + + a { + color: inherit; + font-weight: 700; + text-decoration: underline; + } } // basic banner @@ -622,7 +661,7 @@ pre { } code { - color: #c63e1f; + color: color-functions.color(code); } /* **** */ @@ -657,7 +696,7 @@ code { width: 20%; top: 9.3rem; float: right; - background: color-functions.on-color(active); + background: color-functions.color-alternate(body, 2); } @media screen and (width <= 90.62rem) { @@ -680,7 +719,7 @@ code { .card-header { color: color-functions.on-color(body); - background: rgba(color-functions.on-color(body), 0.03); + background: color-functions.color(card-header); border-bottom-color: color-functions.border-color(body); } @@ -738,7 +777,7 @@ a.partial-underline { } &.background { - background: color-functions.on-color(active); + background: color-functions.color-alternate(body, 2); } } } @@ -795,6 +834,10 @@ a.partial-underline { color: color-functions.on-color-emphasis(body); } +.card-data .card-title { + color: color-functions.color(property-label); +} + .contact-card-title { font-size: 1.1rem; font-weight: bold; @@ -814,7 +857,7 @@ a.partial-underline { max-width: 100%; height: utilities.to-rem(480); margin: 0 auto; - border: 3px solid #dfdfdf; + border: 3px solid color-functions.border-color(body); padding: 3px; display: flex; flex-direction: column; @@ -894,7 +937,7 @@ a.partial-underline { .usa-card__header { @include utilities.font("Roboto-Regular"); - color: color-functions.color(body); + color: color-functions.on-color(secondary); background: color-functions.color(secondary); border-bottom-color: color-functions.border-color(body); border-radius: 0.3rem 0.3rem 0 0; @@ -1024,7 +1067,7 @@ img.yt-core-image { display: inline-block; vertical-align: top; background-position: center; - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg fill='#{color-functions.escape-color(color-functions.on-color(body))}' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); + background-image: var(--attack-select-arrow); z-index: 1; transition: all 0.2s ease; } @@ -1343,7 +1386,7 @@ img.yt-core-image { } .section-shadow { - border-bottom: 1px solid #dfdfdf !important; + border-bottom: 1px solid color-functions.border-color(body) !important; } table { @@ -1372,6 +1415,19 @@ div#sidebars { .attackcons { border-top-width: 0; + .attackcon-banner-image { + display: inline-block; + width: 100%; + box-sizing: border-box; + + &.on-light { + padding: 1rem; + border: 1px solid color-functions.border-color(body); + border-radius: 0.75rem; + background: color-functions.color(image-background); + } + } + .sponsors { flex: 1; padding-left: 25px; @@ -1382,16 +1438,20 @@ div#sidebars { } .sponsors-block { - background: color-functions.on-color(active); + background: color-functions.color(image-background); text-align: center; display: flex; justify-content: space-evenly; flex-wrap: wrap; flex-direction: column; width: 200%; + padding: utilities.to-rem(10); + border: utilities.to-rem(1) solid color-functions.border-color(body); + border-radius: utilities.to-rem(8); + box-sizing: border-box; .img-container { - margin: 10px; + margin: utilities.to-rem(10); flex: 1 1 20%; box-sizing: border-box; @@ -1434,7 +1494,7 @@ div#sidebars { } img.sponsor-logo { - background-color: color-functions.color(body); + background-color: color-functions.color(image-background); border-radius: 6px; object-fit: contain; object-position: center; @@ -1481,7 +1541,7 @@ div#sidebars { .resource { flex: 1; - background-color: color-functions.on-color(active); + background-color: color-functions.color-alternate(body, 2); padding: 10px; box-sizing: border-box; } @@ -1613,7 +1673,7 @@ div#sidebars { } .tip-box { - background: color-functions.on-color(active); + background: color-functions.color-alternate(body, 2); padding: 1rem; } diff --git a/attack-style/layout/_nav.scss b/attack-style/layout/_nav.scss index 85e321a44c3..c25a40d94b4 100644 --- a/attack-style/layout/_nav.scss +++ b/attack-style/layout/_nav.scss @@ -11,6 +11,24 @@ text-decoration: underline; } +@mixin dark-theme-toggle { + .theme-toggle-track { + background: color-functions.color(secondary); + } + + .theme-toggle-icon-light { + color: color-functions.on-color(primary); + } + + .theme-toggle-icon-dark { + color: color-functions.color(secondary); + } + + .theme-toggle-thumb { + transform: translateX(utilities.to-rem(22)); + } +} + /* Top NAVIGATION */ // top navigation bar across the web site .navbar { background-color: color-functions.color(primary); @@ -85,12 +103,100 @@ .search-icon { cursor: pointer; - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg fill='#{color-functions.escape-color(color-functions.on-color(primary))}' xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%23fff' xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); } .error-icon { cursor: default; - background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg fill='#{color-functions.escape-color(color-functions.on-color(primary))}' xmlns='http://www.w3.org/2000/svg' height='24' viewBox='0 96 960 960' width='24'%3e%3cpath d='M479.982 776q14.018 0 23.518-9.482 9.5-9.483 9.5-23.5 0-14.018-9.482-23.518-9.483-9.5-23.5-9.5-14.018 0-23.518 9.482-9.5 9.483-9.5 23.5 0 14.018 9.482 23.518 9.483 9.5 23.5 9.5ZM453 623h60V370h-60v253Zm27.266 353q-82.734 0-155.5-31.5t-127.266-86q-54.5-54.5-86-127.341Q80 658.319 80 575.5q0-82.819 31.5-155.659Q143 347 197.5 293t127.341-85.5Q397.681 176 480.5 176q82.819 0 155.659 31.5Q709 239 763 293t85.5 127Q880 493 880 575.734q0 82.734-31.5 155.5T763 858.316q-54 54.316-127 86Q563 976 480.266 976Zm.234-60Q622 916 721 816.5t99-241Q820 434 721.188 335 622.375 236 480 236q-141 0-240.5 98.812Q140 433.625 140 576q0 141 99.5 240.5t241 99.5Zm-.5-340Z'/%3e%3c/svg%3e"); + background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg fill='%23fff' xmlns='http://www.w3.org/2000/svg' height='24' viewBox='0 96 960 960' width='24'%3e%3cpath d='M479.982 776q14.018 0 23.518-9.482 9.5-9.483 9.5-23.5 0-14.018-9.482-23.518-9.483-9.5-23.5-9.5-14.018 0-23.518 9.482-9.5 9.483-9.5 23.5 0 14.018 9.482 23.518 9.483 9.5 23.5 9.5ZM453 623h60V370h-60v253Zm27.266 353q-82.734 0-155.5-31.5t-127.266-86q-54.5-54.5-86-127.341Q80 658.319 80 575.5q0-82.819 31.5-155.659Q143 347 197.5 293t127.341-85.5Q397.681 176 480.5 176q82.819 0 155.659 31.5Q709 239 763 293t85.5 127Q880 493 880 575.734q0 82.734-31.5 155.5T763 858.316q-54 54.316-127 86Q563 976 480.266 976Zm.234-60Q622 916 721 816.5t99-241Q820 434 721.188 335 622.375 236 480 236q-141 0-240.5 98.812Q140 433.625 140 576q0 141 99.5 240.5t241 99.5Zm-.5-340Z'/%3e%3c/svg%3e"); + } + } + + .theme-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: utilities.to-rem(58); + min-height: utilities.to-rem(38); + margin-right: utilities.to-rem(8); + padding: utilities.to-rem(4); + border: 0; + color: color-functions.on-color(primary); + + .theme-toggle-track { + position: relative; + display: inline-flex; + align-items: center; + justify-content: space-between; + width: utilities.to-rem(50); + height: utilities.to-rem(28); + padding: 0 utilities.to-rem(7); + border: utilities.to-rem(1) solid color-functions.on-color(primary); + border-radius: utilities.to-rem(14); + background: rgb(0 0 0 / 20%); + box-sizing: border-box; + transition: background-color 0.2s ease; + } + + .theme-toggle-icon { + position: relative; + z-index: 2; + visibility: visible; + display: inline-flex; + align-items: center; + justify-content: center; + width: utilities.to-rem(12); + height: utilities.to-rem(12); + font-size: utilities.to-rem(12); + line-height: 1; + } + + &:hover, + &:focus { + color: color-functions.on-color(primary); + + .theme-toggle-track { + background: rgb(0 0 0 / 35%); + box-shadow: 0 0 0 utilities.to-rem(2) rgb(255 255 255 / 30%); + } + } + + &:focus { + outline: 0; + box-shadow: none; + } + + .theme-toggle-icon-light { + color: color-functions.color(primary); + } + + .theme-toggle-thumb { + position: absolute; + top: utilities.to-rem(2); + left: utilities.to-rem(2); + z-index: 1; + width: utilities.to-rem(22); + height: utilities.to-rem(22); + border-radius: 50%; + background: color-functions.on-color(primary); + box-shadow: 0 utilities.to-rem(1) utilities.to-rem(3) rgb(0 0 0 / 35%); + transition: transform 0.2s ease; + } + + &[data-theme-effective="dark"] { + @include dark-theme-toggle; + } + + // The theme script sets the root preference in the document head. Use it to + // paint the saved state before DOMContentLoaded initializes the control. + :root[data-theme="dark"] & { + @include dark-theme-toggle; + } + + @media (prefers-color-scheme: dark) { + :root:not([data-theme]) & { + @include dark-theme-toggle; + } } } } @@ -228,7 +334,7 @@ cursor: col-resize; height: 100%; position: absolute; - background-color: #dfdfdf; + background-color: color-functions.border-color(body); } .data-sources-menu { @@ -286,7 +392,7 @@ .expand-button { // any direct child cursor: pointer; - color: black; + color: color-functions.on-color(body); &:hover { background: color-functions.color-alternate(body); @@ -320,7 +426,7 @@ & > a { color: color-functions.color(active) !important; font-weight: bolder; - background: color-functions.on-color(active); + background: color-functions.color-alternate(body, 2); font-family: Roboto-Bold, sans-serif; } diff --git a/attack-style/package.json b/attack-style/package.json index 490ad27ae26..d84729c00b3 100644 --- a/attack-style/package.json +++ b/attack-style/package.json @@ -8,8 +8,8 @@ "main": "index.js", "scripts": { "clean": "rm -rf dist/", - "build": "sass style-attack.scss dist/style-attack.css && sass style-user.scss dist/style-user.css", - "copy": "cp dist/style-attack.css dist/style-user.css ../attack-theme/static/", + "build": "sass style-attack.scss dist/style-attack.css && sass style-user.scss dist/style-user.css && sass style-archive.scss dist/style-archive.css", + "copy": "cp dist/style-attack.css dist/style-user.css dist/style-archive.css ../attack-theme/static/", "build-copy": "npm run clean && npm run build && npm run copy", "watch": "sass --watch style-attack.scss dist/style-attack.css && sass --watch style-user.scss dist/style-user.css", "lint": "stylelint **/*.scss" diff --git a/attack-style/style-archive.scss b/attack-style/style-archive.scss new file mode 100644 index 00000000000..3cb39748fbf --- /dev/null +++ b/attack-style/style-archive.scss @@ -0,0 +1,3 @@ +// Compatibility colors for preserved ATT&CK sites; keep their original layout. +@use "config" with ($use-attack-theme: true); +@use "themes/archive"; diff --git a/attack-style/style.scss b/attack-style/style.scss index c8c19234ddd..77fe3a8e71d 100644 --- a/attack-style/style.scss +++ b/attack-style/style.scss @@ -21,4 +21,7 @@ @use "components/search"; // Search component styles @use "components/tour"; // Tour component styles @use "components/matrix"; // Matrix component styles -@use "components/versioning"; // Versioning component styles \ No newline at end of file +@use "components/versioning"; // Versioning component styles + +// Emit runtime light/dark palettes and normalize vendor components last. +@use "themes/colors"; diff --git a/attack-style/themes/README.md b/attack-style/themes/README.md index bca278889a1..e16944148e4 100644 --- a/attack-style/themes/README.md +++ b/attack-style/themes/README.md @@ -1,21 +1,34 @@ # Themes -This folder is reserved for theme-specific Sass. +This folder contains the runtime color palettes used by the generated stylesheets. ## Files | File | Purpose | | --- | --- | -| `_colors.scss` | Reserved for theme color overrides or extracted theme color definitions. It is currently empty. | +| `_palette.scss` | Shared light/dark token mixins, including the charcoal surfaces. | +| `_archive.scss` | Scoped color compatibility rules and a banner switch for preserved sites. | +| `_colors.scss` | Emits the light and dark semantic color tokens, system preference behavior, explicit theme overrides, and shared Bootstrap surface adjustments. | ## Active Theme Switch -The active color set is currently controlled by `config.scss` and the two top-level entrypoints: +The site has two independent theme layers. The brand layer is selected at build time by `config.scss` and the two top-level entrypoints: | File | Behavior | | --- | --- | | `style-attack.scss` | Sets `$use-attack-theme: true` and imports the shared style graph. | | `style-user.scss` | Sets `$use-attack-theme: false` and imports the shared style graph. | -Most theme-aware styling should continue to use semantic color helpers from `abstracts/_color-functions.scss`. -Add theme-specific Sass here only when the existing semantic color map is not enough. +The light/dark appearance is selected at runtime. With no `data-theme` attribute on the root element, the stylesheet follows `prefers-color-scheme`. The single navigation toggle stores an explicit `data-theme="light"` or `data-theme="dark"` override after the user first switches themes. + +On collapsed navigation, the same toggle moves immediately left of the hamburger when it fits alongside the logo. If space is insufficient, it returns to its menu slot before Search; desktop keeps that original slot. Placement follows Bootstrap's hamburger visibility and measured element widths, including changes to the logo or browser size. Saved preferences synchronize across pages and open tabs on the same origin and browser profile. + +Most styling should use semantic color helpers from `abstracts/_color-functions.scss`. Add a token to `_palette.scss` when a component needs a distinct theme-aware surface or foreground instead of embedding a light-only color in that component. + +## Preserved Sites + +`style-archive.scss` builds `style-archive.css` alongside the two current-site stylesheets. The versions deployment module adds this stylesheet and the shared theme script to extracted HTML pages with an archive banner. It skips sidebar fragments, redirects, and pages with native theme controls. The stored tarballs are unchanged; every extraction source receives the same idempotent enhancement. + +The script adds a keyboard-accessible “Dark mode” switch to the existing `.version-banner`. It shares the current site's stored appearance preference. The archive stylesheet supplies color overrides only in dark screen mode; light mode and print retain the archive's original presentation. The archive's original styles, content, URLs, logos, and external widgets remain in place. + +Theme scripts set `data-theme` only. CSS owns `color-scheme`, allowing print to use light native controls even when a dark preference is saved. diff --git a/attack-style/themes/_archive.scss b/attack-style/themes/_archive.scss new file mode 100644 index 00000000000..e0e07aba3f2 --- /dev/null +++ b/attack-style/themes/_archive.scss @@ -0,0 +1,337 @@ +@use "palette"; + +@mixin dark-surfaces { + @include palette.dark-colors; + + color-scheme: dark; + + body, + .jumbotron, + .card, + .card-filter, + .card-body, + .contact-card .card-header.no-background, + .contact-card .card-footer.no-background, + .sidebar, + .sidebar .heading, + .matrix-container, + .matrix .technique-cell, + .table-matrix td, + .dropdown-content, + .dropdown-menu, + .form-control, + .custom-select, + .bootstrap-select > .dropdown-toggle, + .modal-content, + .popover, + .popover-body, + .search-results, + .overlay.search .overlay-inner, + .overlay.search .overlay-inner .search-header .search-input input { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body); + border-color: var(--attack-border-color-body); + } + + .table, + .table-light, + .table td, + .table th, + .card-data, + .getting-started-color, + .table-techniques td, + .techniques-used td, + .datasources-table td { + color: var(--attack-on-color-body); + border-color: var(--attack-border-color-body); + } + + a:where(:not(.osano-cm-link)), + .dropdown-item, + .matrix-tactics-url, + .matrix-tactics-url:visited, + .matrix-tactics-url:hover, + .matrix-tactics-url:active, + .table-techniques .sub.technique td:not(:nth-child(4)), + .table-techniques .technique:not(.sub) td:not(:nth-child(3)) { + color: var(--attack-color-link); + } + + .matrix-header { + color: var(--attack-on-color-body-emphasis); + } + + .table-light, + .table-matrix .matrix-header, + .table-alternate tbody, + .blog-post table tbody, + .changelog table tbody { + background-color: var(--attack-color-body); + } + + .bg-white { + background-color: var(--attack-color-body) !important; + } + + .bg-alternate, + .bg-gray, + .bg-light, + .bg-accord-light, + .bg-accord-dark, + .table-alternate, + .table-techniques thead tr, + .techniques-used thead tr, + .datasources-table thead tr, + .table-striped tbody tr:nth-of-type(odd), + .card-header, + .contact-card .card-body.background, + .breadcrumb, + .resource, + .tip-box, + .under-development, + .training .exercise, + .example-container, + .section-view .anchor-section { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body-alternate) !important; + border-color: var(--attack-border-color-body); + } + + .table .active, + .search-results .search-header, + .search-results .search-highlight, + .nav-link.side.active { + color: var(--attack-color-active); + } + + .nav .heading-dropdown, + .faq .heading-dropdown, + .faq .nav-link.expand-title, + .nav .nav-link.expand-title, + .expand-icon, + .card-title { + color: var(--attack-on-color-body-emphasis); + } + + .version-banner, + .version-banner a { + color: var(--attack-on-color-banner); + background-color: var(--attack-color-banner); + } + + .version-banner a { + text-decoration: underline; + } + + .footer a, + .footer .footer-link { + color: var(--attack-on-color-footer); + } + + .table-hover tbody tr:hover, + .dropdown-item:hover, + .dropdown-item:focus, + .nav-link.side:hover, + .sidenav .sidenav-head a:hover, + .sidenav .sidenav-head .expand-button:hover { + color: var(--attack-on-color-body-emphasis); + background-color: var(--attack-color-body-alternate-strong); + } + + .sidenav .sidenav-head.active, + .sidenav .sidenav-head.active > a { + color: var(--attack-color-active) !important; + background-color: var(--attack-color-body-alternate-strong); + } + + .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head a, + .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head .expand-button { + color: var(--attack-on-color-body-emphasis); + } + + .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head a:hover, + .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head .expand-button:hover { + background-color: var(--attack-color-body-alternate-strong); + } + + .nav .nav-link.side.active, + .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head.active, + .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head.active > a { + color: var(--attack-color-active) !important; + background-color: var(--attack-color-body-alternate-strong); + } + + .card-data .card-title { + color: var(--attack-color-property-label); + } + + .deemphasis, + .text-label-small, + .text-muted { + color: var(--attack-on-color-body-deemphasis) !important; + } + + .text-danger, + font[color="red"] { + color: var(--attack-color-danger) !important; + } + + .search-word-found, + mark { + color: var(--attack-on-color-search-highlight); + background-color: var(--attack-color-search-highlight); + } + + .custom-select, + .card-block .card-header::after, + .faq .card-header::after, + .heading-dropdown::after, + .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head .expand-button::after, + #usecases .card-header::after { + background-image: var(--attack-select-arrow); + } + + .resizer { + background-color: var(--attack-border-color-body); + } + + code { + color: var(--attack-color-code); + } + + pre, + .jumbotron code { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body-alternate-strongest); + } + + .btn-default, + .btn-outline-secondary, + .matrix-controls button, + .matrix-controls .layout-button:active, + .slide-button-secondary { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body); + border-color: var(--attack-border-color-body); + } + + .btn-primary, + .footer .btn-primary { + color: var(--attack-on-color-active); + background-color: var(--attack-color-active); + border-color: var(--attack-color-active); + } + + .btn-default:hover, + .btn-outline-secondary:not(:disabled, .disabled):hover, + .btn-outline-secondary:not(:disabled, .disabled):focus, + .matrix-controls button:hover, + .slide-button { + color: var(--attack-on-color-active); + background-color: var(--attack-color-active); + border-color: var(--attack-color-active); + } + + .matrix.side .tactic .handle, + .matrix.flat .tactic .supertechnique td.sidebar.technique .handle { + color: var(--attack-color-body); + background-color: var(--attack-on-color-body-deemphasis); + } + + .matrix.side .sidebar.expanded .angle, + .matrix.side .tactic .sidebar.expanded .angle { + background-color: var(--attack-color-body); + } + + .matrix.side .tactic:hover:not(.name, .count), + .matrix.side .tactic:hover:not(.name, .count) .sidebar.expanded .angle { + background-color: var(--attack-background-color-body); + } + + .matrix-container .scroll-indicator-group .scroll-indicator.right.show .cover { + background: linear-gradient(to right, rgb(255 255 255 / 0.1%), var(--attack-color-body)); + } + + .matrix-container .scroll-indicator-group .scroll-indicator.left.show .cover { + background: linear-gradient(to left, rgb(255 255 255 / 0.1%), var(--attack-color-body)); + } + + .matrix .tactic.count, + .matrix .technique-cell, + .resizer, + hr { + border-color: var(--attack-border-color-body); + } + + // Header/footer retain their original branding. Do not recolor images or + // third-party widgets; their own palettes and functionality belong to them. + .navbar-orange .nav-link, + .navbar .nav-tabs .nav-link, + .nav .dropdown-menu .dropdown-item { + color: white; + } + + .nav .dropdown-menu { + background-color: var(--attack-color-primary); + } + +} + +:root[data-archive-theme] { + @include palette.light-colors; + + color-scheme: light; + + .archive-theme-toggle { + display: inline-flex; + align-items: center; + gap: 0.5em; + margin: 0.25em 0.75em; + padding: 0.35em 0.65em; + min-height: 2.75em; + border: 1px solid currentcolor; + border-radius: 0.3em; + color: inherit; + background: transparent; + font: inherit; + cursor: pointer; + + &::after { + content: ""; + width: 2em; + height: 1em; + border: 1px solid currentcolor; + border-radius: 1em; + background: radial-gradient(circle at 0.5em center, currentcolor 0.3em, transparent 0.35em); + } + + &[aria-checked="true"]::after { + background: radial-gradient(circle at 1.5em center, currentcolor 0.3em, transparent 0.35em); + } + + &:focus-visible { + outline: 2px solid currentcolor; + outline-offset: 3px; + } + } +} + +// Restrict all compatibility overrides to the dark screen appearance. Light +// and print retain the archive's original CSS, including native controls. +@media screen { + :root[data-archive-theme][data-theme="dark"] { + @include dark-surfaces; + } +} + +@media screen and (prefers-color-scheme: dark) { + :root[data-archive-theme]:not([data-theme]) { + @include dark-surfaces; + } +} + +@media print { + :root[data-archive-theme] .archive-theme-toggle { + display: none; + } +} diff --git a/attack-style/themes/_colors.scss b/attack-style/themes/_colors.scss index e69de29bb2d..5a96b3ceaea 100644 --- a/attack-style/themes/_colors.scss +++ b/attack-style/themes/_colors.scss @@ -0,0 +1,130 @@ +@use "palette"; + +:root, +:root[data-theme="light"] { + @include palette.light-colors; + + color-scheme: light; +} + +@media (prefers-color-scheme: dark) { + :root:not([data-theme]) { + @include palette.dark-colors; + + color-scheme: dark; + } +} + +:root[data-theme="dark"] { + @include palette.dark-colors; + + color-scheme: dark; +} + +// Normalize Bootstrap surfaces that otherwise retain hard-coded light colors. +.form-control, +.custom-select, +.bootstrap-select > .dropdown-toggle, +.dropdown-menu, +.list-group-item, +.modal-content, +.popover, +.popover-body, +.page-link, +.input-group-text { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body); + border-color: var(--attack-border-color-body); +} + +.custom-select { + background-image: var(--attack-select-arrow); +} + +.form-control:focus, +.custom-select:focus { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body); + border-color: var(--attack-color-active); + box-shadow: 0 0 0 0.2rem rgb(76 159 254 / 25%); +} + +.dropdown-item, +.page-link { + color: var(--attack-color-link); +} + +.dropdown-item:hover, +.dropdown-item:focus, +.page-link:hover, +.page-link:focus { + color: var(--attack-on-color-body-emphasis); + background-color: var(--attack-color-body-alternate); +} + +.form-control:disabled, +.form-control[readonly], +.custom-select:disabled, +.page-item.disabled .page-link { + color: var(--attack-on-color-body-deemphasis); + background-color: var(--attack-color-body-alternate); +} + +.dropdown-divider, +hr { + border-color: var(--attack-border-color-body); +} + +.table { + color: var(--attack-on-color-body); +} + +.text-danger { + color: var(--attack-color-danger) !important; +} + +.btn-outline-secondary { + color: var(--attack-on-color-body); + border-color: var(--attack-on-color-body-deemphasis); + + &:disabled, + &.disabled { + color: var(--attack-on-color-body-deemphasis); + background-color: transparent; + } + + &:not(:disabled, .disabled):hover, + &:not(:disabled, .disabled):focus, + &:not(:disabled, .disabled):active { + color: var(--attack-on-color-active); + background-color: var(--attack-color-active); + border-color: var(--attack-color-active); + } +} + +.nav-tabs .nav-link { + color: var(--attack-color-link); + border-color: transparent; +} + +.nav-tabs .nav-link:hover, +.nav-tabs .nav-link:focus { + border-color: var(--attack-border-color-body); +} + +.nav-tabs .nav-link.active, +.nav-tabs .nav-item.show .nav-link { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body); + border-color: var(--attack-border-color-body) var(--attack-border-color-body) var(--attack-color-body); +} + +@media print { + :root, + :root[data-theme], + :root:not([data-theme]) { + @include palette.light-colors; + + color-scheme: light; + } +} diff --git a/attack-style/themes/_palette.scss b/attack-style/themes/_palette.scss new file mode 100644 index 00000000000..d074e1ce87f --- /dev/null +++ b/attack-style/themes/_palette.scss @@ -0,0 +1,92 @@ +@use "sass:color"; +@use "sass:map"; +@use "../abstracts/variables"; +@use "../config" as config; + +$primary: map.get(map.get(variables.$colors, primary), color); +$secondary: map.get(map.get(variables.$colors, secondary), color); +$footer: map.get(map.get(variables.$colors, footer), color); +$active: map.get(map.get(variables.$colors, active), color); +$dark-active: if(sass(config.$use-attack-theme): #60a9ff; else: #9aa3a6); + +@mixin light-colors { + --attack-color-primary: #{$primary}; + --attack-on-color-primary: white; + --attack-color-secondary: #{$secondary}; + --attack-color-secondary-hover: #{color.scale($secondary, $lightness: 5%)}; + --attack-on-color-secondary: white; + --attack-color-footer: #{$footer}; + --attack-on-color-footer: #87deff; + --attack-color-footer-link-hover: #{color.mix(#87deff, #3f709e)}; + --attack-color-active: #{$active}; + --attack-color-active-alternate-medium: #{color.mix(color.invert($active), $active, $weight: 7.5%)}; + --attack-on-color-active: #eaeaea; + --attack-color-body: white; + --attack-on-color-body: #39434c; + --attack-on-color-body-emphasis: #1d2226; + --attack-color-property-label: #1d2226; + --attack-on-color-body-deemphasis: #6b7379; + --attack-color-body-alternate-subtle: #f5f5f5; + --attack-color-body-alternate: #f2f2f2; + --attack-color-body-alternate-strong: #e6e6e6; + --attack-color-body-alternate-strongest: #d9d9d9; + --attack-border-color-body: #dfdfdf; + --attack-background-color-body: #dfdfdf; + --attack-color-link: #3f709e; + --attack-color-link-hover: #0056b3; + --attack-color-matrix-header: gray; + --attack-on-color-matrix-header: white; + --attack-color-search-highlight: yellow; + --attack-on-color-search-highlight: black; + --attack-color-deemphasis: #686f75; + --attack-on-color-deemphasis: white; + --attack-color-card-header: rgb(57 67 76 / 3%); + --attack-color-code: #a52f16; + --attack-color-danger: #bd2130; + --attack-color-image-background: white; + --attack-color-banner: #e7f0f6; + --attack-on-color-banner: #263b4a; + --attack-border-color-banner: #c2d5e2; + --attack-select-arrow: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%2339434c' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'/%3E%3C/svg%3E"); +} + +@mixin dark-colors { + --attack-color-primary: #{$primary}; + --attack-on-color-primary: white; + --attack-color-secondary: #{$secondary}; + --attack-color-secondary-hover: #{color.scale($secondary, $lightness: 8%)}; + --attack-on-color-secondary: white; + --attack-color-footer: #{$footer}; + --attack-on-color-footer: #87deff; + --attack-color-footer-link-hover: #b7edff; + --attack-color-active: #{$dark-active}; + --attack-color-active-alternate-medium: #{if(sass(config.$use-attack-theme): #3d8cdb; else: #879195)}; + --attack-on-color-active: #0f171c; + --attack-color-body: #222426; + --attack-on-color-body: #e8e6e3; + --attack-on-color-body-emphasis: #fffaf4; + --attack-color-property-label: #f2d2a4; + --attack-on-color-body-deemphasis: #b7b1a8; + --attack-color-body-alternate-subtle: #272a2c; + --attack-color-body-alternate: #2b2e30; + --attack-color-body-alternate-strong: #303437; + --attack-color-body-alternate-strongest: #353a3d; + --attack-border-color-body: #596166; + --attack-background-color-body: #373d40; + --attack-color-link: #7bb8ee; + --attack-color-link-hover: #b7ddff; + --attack-color-matrix-header: #596166; + --attack-on-color-matrix-header: #fffaf4; + --attack-color-search-highlight: #665a00; + --attack-on-color-search-highlight: #fff4b8; + --attack-color-deemphasis: #b7b1a8; + --attack-on-color-deemphasis: #222426; + --attack-color-card-header: #2b2e30; + --attack-color-code: #ff8f70; + --attack-color-danger: #ff8c96; + --attack-color-image-background: white; + --attack-color-banner: #263a49; + --attack-on-color-banner: #f2f7fa; + --attack-border-color-banner: #3f5d72; + --attack-select-arrow: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%23e8e6e3' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'/%3E%3C/svg%3E"); +} diff --git a/attack-theme/static/scripts/sidebar-load-all.js b/attack-theme/static/scripts/sidebar-load-all.js index 9c3e5672a3f..e6220a56c2f 100644 --- a/attack-theme/static/scripts/sidebar-load-all.js +++ b/attack-theme/static/scripts/sidebar-load-all.js @@ -9,7 +9,7 @@ else{ if (mod_name.includes('contact')){ mod_entry = "/" + "resources/sidebar-resources" } -$("#sidebars").load(mod_entry, function() { +$("#sidebars").load(`${mod_entry}/`, function() { let old_winlocation = window.location.href; if (mod_name.includes('versions')){ let v_number = mod_name[2]; @@ -63,4 +63,4 @@ $("#sidebars").load(mod_entry, function() { }); mediaQuery.addEventListener('change', mobileSidenav) -}); \ No newline at end of file +}); diff --git a/attack-theme/static/scripts/theme.js b/attack-theme/static/scripts/theme.js new file mode 100644 index 00000000000..65349e1f447 --- /dev/null +++ b/attack-theme/static/scripts/theme.js @@ -0,0 +1,243 @@ +(function(globalObject, factory) { + const theme = factory(); + + if (typeof module === 'object' && module.exports) { + module.exports = theme; + } + + if (globalObject && globalObject.document) { + let storage = null; + + try { + storage = globalObject.localStorage; + } catch (error) { + // Accessing localStorage itself can fail in restricted contexts. + } + + theme.bootstrap({ + document: globalObject.document, + archived: !!(globalObject.document.currentScript + && globalObject.document.currentScript.hasAttribute('data-archive-theme')), + mediaQuery: typeof globalObject.matchMedia === 'function' + ? globalObject.matchMedia('(prefers-color-scheme: dark)') + : null, + storage + }); + } +}(typeof window !== 'undefined' ? window : null, function() { + const STORAGE_KEY = 'attack-website-theme'; + const EXPLICIT_PREFERENCES = ['light', 'dark']; + + function isExplicitPreference(preference) { + return EXPLICIT_PREFERENCES.includes(preference); + } + + function readStoredPreference(storage) { + if (!storage) return 'system'; + + try { + const storedPreference = storage.getItem(STORAGE_KEY); + if (isExplicitPreference(storedPreference)) return storedPreference; + if (storedPreference !== null) storage.removeItem(STORAGE_KEY); + } catch (error) { + // Storage may be unavailable in privacy-restricted browser contexts. + } + + return 'system'; + } + + function savePreference(storage, preference) { + if (!storage) return; + + try { + if (isExplicitPreference(preference)) { + storage.setItem(STORAGE_KEY, preference); + } else { + storage.removeItem(STORAGE_KEY); + } + } catch (error) { + // The in-page choice still works when storage is unavailable. + } + } + + function applyPreference(root, preference) { + if (isExplicitPreference(preference)) { + root.setAttribute('data-theme', preference); + } else { + root.removeAttribute('data-theme'); + } + } + + function effectiveTheme(preference, mediaQuery) { + if (isExplicitPreference(preference)) return preference; + return mediaQuery && mediaQuery.matches ? 'dark' : 'light'; + } + + function initNavigationToggle(document, toggle) { + const view = document.defaultView; + const home = document.querySelector('[data-theme-toggle-home]'); + const mobile = document.querySelector('[data-theme-toggle-mobile]'); + if (!view || !home || !mobile || !toggle) return; + + const navbar = mobile.closest('.navbar'); + const brand = navbar.querySelector('.navbar-brand'); + const hamburger = mobile.querySelector('.navbar-toggler'); + let pending = false; + + function outerWidth(element) { + const style = view.getComputedStyle(element); + return element.getBoundingClientRect().width + + parseFloat(style.marginLeft) + parseFloat(style.marginRight); + } + + function updatePlacement() { + pending = false; + const focused = document.activeElement === toggle; + let destination = home; + + // Bootstrap controls the breakpoint; measure only when the hamburger is shown. + if (view.getComputedStyle(hamburger).display !== 'none') { + if (toggle.parentNode !== mobile) mobile.insertBefore(toggle, hamburger); + const style = view.getComputedStyle(navbar); + const availableWidth = navbar.clientWidth + - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight); + if (outerWidth(brand) + mobile.getBoundingClientRect().width <= availableWidth) { + destination = mobile; + } + } + + if (toggle.parentNode !== destination) destination.appendChild(toggle); + home.hidden = destination === mobile; + + // Moving a focused node can blur it. If it now lives in the closed menu, + // leave focus on the button that opens that menu instead. + if (focused) { + const focusTarget = toggle.getClientRects().length ? toggle : hamburger; + focusTarget.focus({ preventScroll: true }); + } + } + + function schedulePlacement() { + if (pending) return; + pending = true; + view.requestAnimationFrame(updatePlacement); + } + + updatePlacement(); + view.addEventListener('resize', schedulePlacement); + if (typeof view.ResizeObserver === 'function') { + const observer = new view.ResizeObserver(schedulePlacement); + [navbar, brand, hamburger, toggle].forEach(element => observer.observe(element)); + } + } + + function createThemeController({ document, storage, mediaQuery, archived = false }) { + const systemTheme = mediaQuery || { matches: false }; + let preference = readStoredPreference(storage); + let toggle; + + function updateControl() { + if (!toggle) return; + + const currentTheme = effectiveTheme(preference, systemTheme); + const nextTheme = currentTheme === 'dark' ? 'light' : 'dark'; + const label = `Switch to ${nextTheme} mode`; + toggle.setAttribute('aria-label', archived ? 'Dark mode' : label); + toggle.setAttribute('title', label); + toggle.setAttribute('aria-checked', String(currentTheme === 'dark')); + toggle.setAttribute('data-theme-effective', currentTheme); + } + + function setPreference(nextPreference) { + preference = isExplicitPreference(nextPreference) ? nextPreference : 'system'; + applyPreference(document.documentElement, preference); + savePreference(storage, preference); + updateControl(); + } + + function handleToggleClick(event) { + if (!event.target || typeof event.target.closest !== 'function') return; + + const clickedToggle = event.target.closest('[data-theme-toggle]'); + if (!clickedToggle) return; + + event.preventDefault(); + toggle = clickedToggle; + const currentTheme = effectiveTheme(preference, systemTheme); + setPreference(currentTheme === 'dark' ? 'light' : 'dark'); + } + + // The head script runs before the toggle markup is parsed. Delegation makes + // the visible control interactive without waiting for DOMContentLoaded. + document.addEventListener('click', handleToggleClick); + + function handleSystemThemeChange() { + if (preference === 'system') updateControl(); + } + + function handleStorageChange(event) { + if (!storage || event.storageArea !== storage + || (event.key !== STORAGE_KEY && event.key !== null)) return; + + preference = readStoredPreference(storage); + applyPreference(document.documentElement, preference); + updateControl(); + } + + function init() { + applyPreference(document.documentElement, preference); + toggle = document.querySelector('[data-theme-toggle]'); + + if (archived && !toggle) { + const banner = document.querySelector('.version-banner'); + if (banner) { + toggle = document.createElement('button'); + toggle.className = 'archive-theme-toggle'; + toggle.type = 'button'; + toggle.setAttribute('role', 'switch'); + toggle.setAttribute('data-theme-toggle', ''); + toggle.textContent = 'Dark mode'; + banner.appendChild(toggle); + } + } + + if (typeof systemTheme.addEventListener === 'function') { + systemTheme.addEventListener('change', handleSystemThemeChange); + } else if (typeof systemTheme.addListener === 'function') { + systemTheme.addListener(handleSystemThemeChange); + } + + if (document.defaultView) { + document.defaultView.addEventListener('storage', handleStorageChange); + } + if (!archived) initNavigationToggle(document, toggle); + updateControl(); + } + + return { init, setPreference }; + } + + function bootstrap({ document, storage, mediaQuery, archived = false }) { + if (archived) document.documentElement.setAttribute('data-archive-theme', ''); + const preference = readStoredPreference(storage); + applyPreference(document.documentElement, preference); + const controller = createThemeController({ document, storage, mediaQuery, archived }); + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', controller.init, { once: true }); + } else { + controller.init(); + } + + return controller; + } + + return { + STORAGE_KEY, + applyPreference, + bootstrap, + createThemeController, + effectiveTheme, + readStoredPreference + }; +})); diff --git a/attack-theme/static/style-archive.css b/attack-theme/static/style-archive.css new file mode 100644 index 00000000000..141e6689a8d --- /dev/null +++ b/attack-theme/static/style-archive.css @@ -0,0 +1,629 @@ +/* COLORS */ +:root[data-archive-theme] { + --attack-color-primary: #c63f1f; + --attack-on-color-primary: white; + --attack-color-secondary: #062f4f; + --attack-color-secondary-hover: rgb(7.5, 58.75, 98.75); + --attack-on-color-secondary: white; + --attack-color-footer: #0b2338; + --attack-on-color-footer: #87deff; + --attack-color-footer-link-hover: rgb(99, 167, 206.5); + --attack-color-active: #0156b3; + --attack-color-active-alternate-medium: rgb(19.975, 92.225, 171.275); + --attack-on-color-active: #eaeaea; + --attack-color-body: white; + --attack-on-color-body: #39434c; + --attack-on-color-body-emphasis: #1d2226; + --attack-color-property-label: #1d2226; + --attack-on-color-body-deemphasis: #6b7379; + --attack-color-body-alternate-subtle: #f5f5f5; + --attack-color-body-alternate: #f2f2f2; + --attack-color-body-alternate-strong: #e6e6e6; + --attack-color-body-alternate-strongest: #d9d9d9; + --attack-border-color-body: #dfdfdf; + --attack-background-color-body: #dfdfdf; + --attack-color-link: #3f709e; + --attack-color-link-hover: #0056b3; + --attack-color-matrix-header: gray; + --attack-on-color-matrix-header: white; + --attack-color-search-highlight: yellow; + --attack-on-color-search-highlight: black; + --attack-color-deemphasis: #686f75; + --attack-on-color-deemphasis: white; + --attack-color-card-header: rgb(57 67 76 / 3%); + --attack-color-code: #a52f16; + --attack-color-danger: #bd2130; + --attack-color-image-background: white; + --attack-color-banner: #e7f0f6; + --attack-on-color-banner: #263b4a; + --attack-border-color-banner: #c2d5e2; + --attack-select-arrow: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%2339434c' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'/%3E%3C/svg%3E"); + color-scheme: light; +} +:root[data-archive-theme] .archive-theme-toggle { + display: inline-flex; + align-items: center; + gap: 0.5em; + margin: 0.25em 0.75em; + padding: 0.35em 0.65em; + min-height: 2.75em; + border: 1px solid currentcolor; + border-radius: 0.3em; + color: inherit; + background: transparent; + font: inherit; + cursor: pointer; +} +:root[data-archive-theme] .archive-theme-toggle::after { + content: ""; + width: 2em; + height: 1em; + border: 1px solid currentcolor; + border-radius: 1em; + background: radial-gradient(circle at 0.5em center, currentcolor 0.3em, transparent 0.35em); +} +:root[data-archive-theme] .archive-theme-toggle[aria-checked=true]::after { + background: radial-gradient(circle at 1.5em center, currentcolor 0.3em, transparent 0.35em); +} +:root[data-archive-theme] .archive-theme-toggle:focus-visible { + outline: 2px solid currentcolor; + outline-offset: 3px; +} + +@media screen { + :root[data-archive-theme][data-theme=dark] { + --attack-color-primary: #c63f1f; + --attack-on-color-primary: white; + --attack-color-secondary: #062f4f; + --attack-color-secondary-hover: rgb(8.4, 65.8, 110.6); + --attack-on-color-secondary: white; + --attack-color-footer: #0b2338; + --attack-on-color-footer: #87deff; + --attack-color-footer-link-hover: #b7edff; + --attack-color-active: #60a9ff; + --attack-color-active-alternate-medium: #3d8cdb; + --attack-on-color-active: #0f171c; + --attack-color-body: #222426; + --attack-on-color-body: #e8e6e3; + --attack-on-color-body-emphasis: #fffaf4; + --attack-color-property-label: #f2d2a4; + --attack-on-color-body-deemphasis: #b7b1a8; + --attack-color-body-alternate-subtle: #272a2c; + --attack-color-body-alternate: #2b2e30; + --attack-color-body-alternate-strong: #303437; + --attack-color-body-alternate-strongest: #353a3d; + --attack-border-color-body: #596166; + --attack-background-color-body: #373d40; + --attack-color-link: #7bb8ee; + --attack-color-link-hover: #b7ddff; + --attack-color-matrix-header: #596166; + --attack-on-color-matrix-header: #fffaf4; + --attack-color-search-highlight: #665a00; + --attack-on-color-search-highlight: #fff4b8; + --attack-color-deemphasis: #b7b1a8; + --attack-on-color-deemphasis: #222426; + --attack-color-card-header: #2b2e30; + --attack-color-code: #ff8f70; + --attack-color-danger: #ff8c96; + --attack-color-image-background: white; + --attack-color-banner: #263a49; + --attack-on-color-banner: #f2f7fa; + --attack-border-color-banner: #3f5d72; + --attack-select-arrow: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%23e8e6e3' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'/%3E%3C/svg%3E"); + color-scheme: dark; + } + :root[data-archive-theme][data-theme=dark] body, + :root[data-archive-theme][data-theme=dark] .jumbotron, + :root[data-archive-theme][data-theme=dark] .card, + :root[data-archive-theme][data-theme=dark] .card-filter, + :root[data-archive-theme][data-theme=dark] .card-body, + :root[data-archive-theme][data-theme=dark] .contact-card .card-header.no-background, + :root[data-archive-theme][data-theme=dark] .contact-card .card-footer.no-background, + :root[data-archive-theme][data-theme=dark] .sidebar, + :root[data-archive-theme][data-theme=dark] .sidebar .heading, + :root[data-archive-theme][data-theme=dark] .matrix-container, + :root[data-archive-theme][data-theme=dark] .matrix .technique-cell, + :root[data-archive-theme][data-theme=dark] .table-matrix td, + :root[data-archive-theme][data-theme=dark] .dropdown-content, + :root[data-archive-theme][data-theme=dark] .dropdown-menu, + :root[data-archive-theme][data-theme=dark] .form-control, + :root[data-archive-theme][data-theme=dark] .custom-select, + :root[data-archive-theme][data-theme=dark] .bootstrap-select > .dropdown-toggle, + :root[data-archive-theme][data-theme=dark] .modal-content, + :root[data-archive-theme][data-theme=dark] .popover, + :root[data-archive-theme][data-theme=dark] .popover-body, + :root[data-archive-theme][data-theme=dark] .search-results, + :root[data-archive-theme][data-theme=dark] .overlay.search .overlay-inner, + :root[data-archive-theme][data-theme=dark] .overlay.search .overlay-inner .search-header .search-input input { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body); + border-color: var(--attack-border-color-body); + } + :root[data-archive-theme][data-theme=dark] .table, + :root[data-archive-theme][data-theme=dark] .table-light, + :root[data-archive-theme][data-theme=dark] .table td, + :root[data-archive-theme][data-theme=dark] .table th, + :root[data-archive-theme][data-theme=dark] .card-data, + :root[data-archive-theme][data-theme=dark] .getting-started-color, + :root[data-archive-theme][data-theme=dark] .table-techniques td, + :root[data-archive-theme][data-theme=dark] .techniques-used td, + :root[data-archive-theme][data-theme=dark] .datasources-table td { + color: var(--attack-on-color-body); + border-color: var(--attack-border-color-body); + } + :root[data-archive-theme][data-theme=dark] a:where(:not(.osano-cm-link)), + :root[data-archive-theme][data-theme=dark] .dropdown-item, + :root[data-archive-theme][data-theme=dark] .matrix-tactics-url, + :root[data-archive-theme][data-theme=dark] .matrix-tactics-url:visited, + :root[data-archive-theme][data-theme=dark] .matrix-tactics-url:hover, + :root[data-archive-theme][data-theme=dark] .matrix-tactics-url:active, + :root[data-archive-theme][data-theme=dark] .table-techniques .sub.technique td:not(:nth-child(4)), + :root[data-archive-theme][data-theme=dark] .table-techniques .technique:not(.sub) td:not(:nth-child(3)) { + color: var(--attack-color-link); + } + :root[data-archive-theme][data-theme=dark] .matrix-header { + color: var(--attack-on-color-body-emphasis); + } + :root[data-archive-theme][data-theme=dark] .table-light, + :root[data-archive-theme][data-theme=dark] .table-matrix .matrix-header, + :root[data-archive-theme][data-theme=dark] .table-alternate tbody, + :root[data-archive-theme][data-theme=dark] .blog-post table tbody, + :root[data-archive-theme][data-theme=dark] .changelog table tbody { + background-color: var(--attack-color-body); + } + :root[data-archive-theme][data-theme=dark] .bg-white { + background-color: var(--attack-color-body) !important; + } + :root[data-archive-theme][data-theme=dark] .bg-alternate, + :root[data-archive-theme][data-theme=dark] .bg-gray, + :root[data-archive-theme][data-theme=dark] .bg-light, + :root[data-archive-theme][data-theme=dark] .bg-accord-light, + :root[data-archive-theme][data-theme=dark] .bg-accord-dark, + :root[data-archive-theme][data-theme=dark] .table-alternate, + :root[data-archive-theme][data-theme=dark] .table-techniques thead tr, + :root[data-archive-theme][data-theme=dark] .techniques-used thead tr, + :root[data-archive-theme][data-theme=dark] .datasources-table thead tr, + :root[data-archive-theme][data-theme=dark] .table-striped tbody tr:nth-of-type(odd), + :root[data-archive-theme][data-theme=dark] .card-header, + :root[data-archive-theme][data-theme=dark] .contact-card .card-body.background, + :root[data-archive-theme][data-theme=dark] .breadcrumb, + :root[data-archive-theme][data-theme=dark] .resource, + :root[data-archive-theme][data-theme=dark] .tip-box, + :root[data-archive-theme][data-theme=dark] .under-development, + :root[data-archive-theme][data-theme=dark] .training .exercise, + :root[data-archive-theme][data-theme=dark] .example-container, + :root[data-archive-theme][data-theme=dark] .section-view .anchor-section { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body-alternate) !important; + border-color: var(--attack-border-color-body); + } + :root[data-archive-theme][data-theme=dark] .table .active, + :root[data-archive-theme][data-theme=dark] .search-results .search-header, + :root[data-archive-theme][data-theme=dark] .search-results .search-highlight, + :root[data-archive-theme][data-theme=dark] .nav-link.side.active { + color: var(--attack-color-active); + } + :root[data-archive-theme][data-theme=dark] .nav .heading-dropdown, + :root[data-archive-theme][data-theme=dark] .faq .heading-dropdown, + :root[data-archive-theme][data-theme=dark] .faq .nav-link.expand-title, + :root[data-archive-theme][data-theme=dark] .nav .nav-link.expand-title, + :root[data-archive-theme][data-theme=dark] .expand-icon, + :root[data-archive-theme][data-theme=dark] .card-title { + color: var(--attack-on-color-body-emphasis); + } + :root[data-archive-theme][data-theme=dark] .version-banner, + :root[data-archive-theme][data-theme=dark] .version-banner a { + color: var(--attack-on-color-banner); + background-color: var(--attack-color-banner); + } + :root[data-archive-theme][data-theme=dark] .version-banner a { + text-decoration: underline; + } + :root[data-archive-theme][data-theme=dark] .footer a, + :root[data-archive-theme][data-theme=dark] .footer .footer-link { + color: var(--attack-on-color-footer); + } + :root[data-archive-theme][data-theme=dark] .table-hover tbody tr:hover, + :root[data-archive-theme][data-theme=dark] .dropdown-item:hover, + :root[data-archive-theme][data-theme=dark] .dropdown-item:focus, + :root[data-archive-theme][data-theme=dark] .nav-link.side:hover, + :root[data-archive-theme][data-theme=dark] .sidenav .sidenav-head a:hover, + :root[data-archive-theme][data-theme=dark] .sidenav .sidenav-head .expand-button:hover { + color: var(--attack-on-color-body-emphasis); + background-color: var(--attack-color-body-alternate-strong); + } + :root[data-archive-theme][data-theme=dark] .sidenav .sidenav-head.active, + :root[data-archive-theme][data-theme=dark] .sidenav .sidenav-head.active > a { + color: var(--attack-color-active) !important; + background-color: var(--attack-color-body-alternate-strong); + } + :root[data-archive-theme][data-theme=dark] .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head a, + :root[data-archive-theme][data-theme=dark] .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head .expand-button { + color: var(--attack-on-color-body-emphasis); + } + :root[data-archive-theme][data-theme=dark] .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head a:hover, + :root[data-archive-theme][data-theme=dark] .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head .expand-button:hover { + background-color: var(--attack-color-body-alternate-strong); + } + :root[data-archive-theme][data-theme=dark] .nav .nav-link.side.active, + :root[data-archive-theme][data-theme=dark] .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head.active, + :root[data-archive-theme][data-theme=dark] .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head.active > a { + color: var(--attack-color-active) !important; + background-color: var(--attack-color-body-alternate-strong); + } + :root[data-archive-theme][data-theme=dark] .card-data .card-title { + color: var(--attack-color-property-label); + } + :root[data-archive-theme][data-theme=dark] .deemphasis, + :root[data-archive-theme][data-theme=dark] .text-label-small, + :root[data-archive-theme][data-theme=dark] .text-muted { + color: var(--attack-on-color-body-deemphasis) !important; + } + :root[data-archive-theme][data-theme=dark] .text-danger, + :root[data-archive-theme][data-theme=dark] font[color=red] { + color: var(--attack-color-danger) !important; + } + :root[data-archive-theme][data-theme=dark] .search-word-found, + :root[data-archive-theme][data-theme=dark] mark { + color: var(--attack-on-color-search-highlight); + background-color: var(--attack-color-search-highlight); + } + :root[data-archive-theme][data-theme=dark] .custom-select, + :root[data-archive-theme][data-theme=dark] .card-block .card-header::after, + :root[data-archive-theme][data-theme=dark] .faq .card-header::after, + :root[data-archive-theme][data-theme=dark] .heading-dropdown::after, + :root[data-archive-theme][data-theme=dark] .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head .expand-button::after, + :root[data-archive-theme][data-theme=dark] #usecases .card-header::after { + background-image: var(--attack-select-arrow); + } + :root[data-archive-theme][data-theme=dark] .resizer { + background-color: var(--attack-border-color-body); + } + :root[data-archive-theme][data-theme=dark] code { + color: var(--attack-color-code); + } + :root[data-archive-theme][data-theme=dark] pre, + :root[data-archive-theme][data-theme=dark] .jumbotron code { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body-alternate-strongest); + } + :root[data-archive-theme][data-theme=dark] .btn-default, + :root[data-archive-theme][data-theme=dark] .btn-outline-secondary, + :root[data-archive-theme][data-theme=dark] .matrix-controls button, + :root[data-archive-theme][data-theme=dark] .matrix-controls .layout-button:active, + :root[data-archive-theme][data-theme=dark] .slide-button-secondary { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body); + border-color: var(--attack-border-color-body); + } + :root[data-archive-theme][data-theme=dark] .btn-primary, + :root[data-archive-theme][data-theme=dark] .footer .btn-primary { + color: var(--attack-on-color-active); + background-color: var(--attack-color-active); + border-color: var(--attack-color-active); + } + :root[data-archive-theme][data-theme=dark] .btn-default:hover, + :root[data-archive-theme][data-theme=dark] .btn-outline-secondary:not(:disabled, .disabled):hover, + :root[data-archive-theme][data-theme=dark] .btn-outline-secondary:not(:disabled, .disabled):focus, + :root[data-archive-theme][data-theme=dark] .matrix-controls button:hover, + :root[data-archive-theme][data-theme=dark] .slide-button { + color: var(--attack-on-color-active); + background-color: var(--attack-color-active); + border-color: var(--attack-color-active); + } + :root[data-archive-theme][data-theme=dark] .matrix.side .tactic .handle, + :root[data-archive-theme][data-theme=dark] .matrix.flat .tactic .supertechnique td.sidebar.technique .handle { + color: var(--attack-color-body); + background-color: var(--attack-on-color-body-deemphasis); + } + :root[data-archive-theme][data-theme=dark] .matrix.side .sidebar.expanded .angle, + :root[data-archive-theme][data-theme=dark] .matrix.side .tactic .sidebar.expanded .angle { + background-color: var(--attack-color-body); + } + :root[data-archive-theme][data-theme=dark] .matrix.side .tactic:hover:not(.name, .count), + :root[data-archive-theme][data-theme=dark] .matrix.side .tactic:hover:not(.name, .count) .sidebar.expanded .angle { + background-color: var(--attack-background-color-body); + } + :root[data-archive-theme][data-theme=dark] .matrix-container .scroll-indicator-group .scroll-indicator.right.show .cover { + background: linear-gradient(to right, rgba(255, 255, 255, 0.001), var(--attack-color-body)); + } + :root[data-archive-theme][data-theme=dark] .matrix-container .scroll-indicator-group .scroll-indicator.left.show .cover { + background: linear-gradient(to left, rgba(255, 255, 255, 0.001), var(--attack-color-body)); + } + :root[data-archive-theme][data-theme=dark] .matrix .tactic.count, + :root[data-archive-theme][data-theme=dark] .matrix .technique-cell, + :root[data-archive-theme][data-theme=dark] .resizer, + :root[data-archive-theme][data-theme=dark] hr { + border-color: var(--attack-border-color-body); + } + :root[data-archive-theme][data-theme=dark] .navbar-orange .nav-link, + :root[data-archive-theme][data-theme=dark] .navbar .nav-tabs .nav-link, + :root[data-archive-theme][data-theme=dark] .nav .dropdown-menu .dropdown-item { + color: white; + } + :root[data-archive-theme][data-theme=dark] .nav .dropdown-menu { + background-color: var(--attack-color-primary); + } +} +@media screen and (prefers-color-scheme: dark) { + :root[data-archive-theme]:not([data-theme]) { + --attack-color-primary: #c63f1f; + --attack-on-color-primary: white; + --attack-color-secondary: #062f4f; + --attack-color-secondary-hover: rgb(8.4, 65.8, 110.6); + --attack-on-color-secondary: white; + --attack-color-footer: #0b2338; + --attack-on-color-footer: #87deff; + --attack-color-footer-link-hover: #b7edff; + --attack-color-active: #60a9ff; + --attack-color-active-alternate-medium: #3d8cdb; + --attack-on-color-active: #0f171c; + --attack-color-body: #222426; + --attack-on-color-body: #e8e6e3; + --attack-on-color-body-emphasis: #fffaf4; + --attack-color-property-label: #f2d2a4; + --attack-on-color-body-deemphasis: #b7b1a8; + --attack-color-body-alternate-subtle: #272a2c; + --attack-color-body-alternate: #2b2e30; + --attack-color-body-alternate-strong: #303437; + --attack-color-body-alternate-strongest: #353a3d; + --attack-border-color-body: #596166; + --attack-background-color-body: #373d40; + --attack-color-link: #7bb8ee; + --attack-color-link-hover: #b7ddff; + --attack-color-matrix-header: #596166; + --attack-on-color-matrix-header: #fffaf4; + --attack-color-search-highlight: #665a00; + --attack-on-color-search-highlight: #fff4b8; + --attack-color-deemphasis: #b7b1a8; + --attack-on-color-deemphasis: #222426; + --attack-color-card-header: #2b2e30; + --attack-color-code: #ff8f70; + --attack-color-danger: #ff8c96; + --attack-color-image-background: white; + --attack-color-banner: #263a49; + --attack-on-color-banner: #f2f7fa; + --attack-border-color-banner: #3f5d72; + --attack-select-arrow: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%23e8e6e3' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'/%3E%3C/svg%3E"); + color-scheme: dark; + } + :root[data-archive-theme]:not([data-theme]) body, + :root[data-archive-theme]:not([data-theme]) .jumbotron, + :root[data-archive-theme]:not([data-theme]) .card, + :root[data-archive-theme]:not([data-theme]) .card-filter, + :root[data-archive-theme]:not([data-theme]) .card-body, + :root[data-archive-theme]:not([data-theme]) .contact-card .card-header.no-background, + :root[data-archive-theme]:not([data-theme]) .contact-card .card-footer.no-background, + :root[data-archive-theme]:not([data-theme]) .sidebar, + :root[data-archive-theme]:not([data-theme]) .sidebar .heading, + :root[data-archive-theme]:not([data-theme]) .matrix-container, + :root[data-archive-theme]:not([data-theme]) .matrix .technique-cell, + :root[data-archive-theme]:not([data-theme]) .table-matrix td, + :root[data-archive-theme]:not([data-theme]) .dropdown-content, + :root[data-archive-theme]:not([data-theme]) .dropdown-menu, + :root[data-archive-theme]:not([data-theme]) .form-control, + :root[data-archive-theme]:not([data-theme]) .custom-select, + :root[data-archive-theme]:not([data-theme]) .bootstrap-select > .dropdown-toggle, + :root[data-archive-theme]:not([data-theme]) .modal-content, + :root[data-archive-theme]:not([data-theme]) .popover, + :root[data-archive-theme]:not([data-theme]) .popover-body, + :root[data-archive-theme]:not([data-theme]) .search-results, + :root[data-archive-theme]:not([data-theme]) .overlay.search .overlay-inner, + :root[data-archive-theme]:not([data-theme]) .overlay.search .overlay-inner .search-header .search-input input { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body); + border-color: var(--attack-border-color-body); + } + :root[data-archive-theme]:not([data-theme]) .table, + :root[data-archive-theme]:not([data-theme]) .table-light, + :root[data-archive-theme]:not([data-theme]) .table td, + :root[data-archive-theme]:not([data-theme]) .table th, + :root[data-archive-theme]:not([data-theme]) .card-data, + :root[data-archive-theme]:not([data-theme]) .getting-started-color, + :root[data-archive-theme]:not([data-theme]) .table-techniques td, + :root[data-archive-theme]:not([data-theme]) .techniques-used td, + :root[data-archive-theme]:not([data-theme]) .datasources-table td { + color: var(--attack-on-color-body); + border-color: var(--attack-border-color-body); + } + :root[data-archive-theme]:not([data-theme]) a:where(:not(.osano-cm-link)), + :root[data-archive-theme]:not([data-theme]) .dropdown-item, + :root[data-archive-theme]:not([data-theme]) .matrix-tactics-url, + :root[data-archive-theme]:not([data-theme]) .matrix-tactics-url:visited, + :root[data-archive-theme]:not([data-theme]) .matrix-tactics-url:hover, + :root[data-archive-theme]:not([data-theme]) .matrix-tactics-url:active, + :root[data-archive-theme]:not([data-theme]) .table-techniques .sub.technique td:not(:nth-child(4)), + :root[data-archive-theme]:not([data-theme]) .table-techniques .technique:not(.sub) td:not(:nth-child(3)) { + color: var(--attack-color-link); + } + :root[data-archive-theme]:not([data-theme]) .matrix-header { + color: var(--attack-on-color-body-emphasis); + } + :root[data-archive-theme]:not([data-theme]) .table-light, + :root[data-archive-theme]:not([data-theme]) .table-matrix .matrix-header, + :root[data-archive-theme]:not([data-theme]) .table-alternate tbody, + :root[data-archive-theme]:not([data-theme]) .blog-post table tbody, + :root[data-archive-theme]:not([data-theme]) .changelog table tbody { + background-color: var(--attack-color-body); + } + :root[data-archive-theme]:not([data-theme]) .bg-white { + background-color: var(--attack-color-body) !important; + } + :root[data-archive-theme]:not([data-theme]) .bg-alternate, + :root[data-archive-theme]:not([data-theme]) .bg-gray, + :root[data-archive-theme]:not([data-theme]) .bg-light, + :root[data-archive-theme]:not([data-theme]) .bg-accord-light, + :root[data-archive-theme]:not([data-theme]) .bg-accord-dark, + :root[data-archive-theme]:not([data-theme]) .table-alternate, + :root[data-archive-theme]:not([data-theme]) .table-techniques thead tr, + :root[data-archive-theme]:not([data-theme]) .techniques-used thead tr, + :root[data-archive-theme]:not([data-theme]) .datasources-table thead tr, + :root[data-archive-theme]:not([data-theme]) .table-striped tbody tr:nth-of-type(odd), + :root[data-archive-theme]:not([data-theme]) .card-header, + :root[data-archive-theme]:not([data-theme]) .contact-card .card-body.background, + :root[data-archive-theme]:not([data-theme]) .breadcrumb, + :root[data-archive-theme]:not([data-theme]) .resource, + :root[data-archive-theme]:not([data-theme]) .tip-box, + :root[data-archive-theme]:not([data-theme]) .under-development, + :root[data-archive-theme]:not([data-theme]) .training .exercise, + :root[data-archive-theme]:not([data-theme]) .example-container, + :root[data-archive-theme]:not([data-theme]) .section-view .anchor-section { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body-alternate) !important; + border-color: var(--attack-border-color-body); + } + :root[data-archive-theme]:not([data-theme]) .table .active, + :root[data-archive-theme]:not([data-theme]) .search-results .search-header, + :root[data-archive-theme]:not([data-theme]) .search-results .search-highlight, + :root[data-archive-theme]:not([data-theme]) .nav-link.side.active { + color: var(--attack-color-active); + } + :root[data-archive-theme]:not([data-theme]) .nav .heading-dropdown, + :root[data-archive-theme]:not([data-theme]) .faq .heading-dropdown, + :root[data-archive-theme]:not([data-theme]) .faq .nav-link.expand-title, + :root[data-archive-theme]:not([data-theme]) .nav .nav-link.expand-title, + :root[data-archive-theme]:not([data-theme]) .expand-icon, + :root[data-archive-theme]:not([data-theme]) .card-title { + color: var(--attack-on-color-body-emphasis); + } + :root[data-archive-theme]:not([data-theme]) .version-banner, + :root[data-archive-theme]:not([data-theme]) .version-banner a { + color: var(--attack-on-color-banner); + background-color: var(--attack-color-banner); + } + :root[data-archive-theme]:not([data-theme]) .version-banner a { + text-decoration: underline; + } + :root[data-archive-theme]:not([data-theme]) .footer a, + :root[data-archive-theme]:not([data-theme]) .footer .footer-link { + color: var(--attack-on-color-footer); + } + :root[data-archive-theme]:not([data-theme]) .table-hover tbody tr:hover, + :root[data-archive-theme]:not([data-theme]) .dropdown-item:hover, + :root[data-archive-theme]:not([data-theme]) .dropdown-item:focus, + :root[data-archive-theme]:not([data-theme]) .nav-link.side:hover, + :root[data-archive-theme]:not([data-theme]) .sidenav .sidenav-head a:hover, + :root[data-archive-theme]:not([data-theme]) .sidenav .sidenav-head .expand-button:hover { + color: var(--attack-on-color-body-emphasis); + background-color: var(--attack-color-body-alternate-strong); + } + :root[data-archive-theme]:not([data-theme]) .sidenav .sidenav-head.active, + :root[data-archive-theme]:not([data-theme]) .sidenav .sidenav-head.active > a { + color: var(--attack-color-active) !important; + background-color: var(--attack-color-body-alternate-strong); + } + :root[data-archive-theme]:not([data-theme]) .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head a, + :root[data-archive-theme]:not([data-theme]) .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head .expand-button { + color: var(--attack-on-color-body-emphasis); + } + :root[data-archive-theme]:not([data-theme]) .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head a:hover, + :root[data-archive-theme]:not([data-theme]) .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head .expand-button:hover { + background-color: var(--attack-color-body-alternate-strong); + } + :root[data-archive-theme]:not([data-theme]) .nav .nav-link.side.active, + :root[data-archive-theme]:not([data-theme]) .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head.active, + :root[data-archive-theme]:not([data-theme]) .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head.active > a { + color: var(--attack-color-active) !important; + background-color: var(--attack-color-body-alternate-strong); + } + :root[data-archive-theme]:not([data-theme]) .card-data .card-title { + color: var(--attack-color-property-label); + } + :root[data-archive-theme]:not([data-theme]) .deemphasis, + :root[data-archive-theme]:not([data-theme]) .text-label-small, + :root[data-archive-theme]:not([data-theme]) .text-muted { + color: var(--attack-on-color-body-deemphasis) !important; + } + :root[data-archive-theme]:not([data-theme]) .text-danger, + :root[data-archive-theme]:not([data-theme]) font[color=red] { + color: var(--attack-color-danger) !important; + } + :root[data-archive-theme]:not([data-theme]) .search-word-found, + :root[data-archive-theme]:not([data-theme]) mark { + color: var(--attack-on-color-search-highlight); + background-color: var(--attack-color-search-highlight); + } + :root[data-archive-theme]:not([data-theme]) .custom-select, + :root[data-archive-theme]:not([data-theme]) .card-block .card-header::after, + :root[data-archive-theme]:not([data-theme]) .faq .card-header::after, + :root[data-archive-theme]:not([data-theme]) .heading-dropdown::after, + :root[data-archive-theme]:not([data-theme]) .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head .expand-button::after, + :root[data-archive-theme]:not([data-theme]) #usecases .card-header::after { + background-image: var(--attack-select-arrow); + } + :root[data-archive-theme]:not([data-theme]) .resizer { + background-color: var(--attack-border-color-body); + } + :root[data-archive-theme]:not([data-theme]) code { + color: var(--attack-color-code); + } + :root[data-archive-theme]:not([data-theme]) pre, + :root[data-archive-theme]:not([data-theme]) .jumbotron code { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body-alternate-strongest); + } + :root[data-archive-theme]:not([data-theme]) .btn-default, + :root[data-archive-theme]:not([data-theme]) .btn-outline-secondary, + :root[data-archive-theme]:not([data-theme]) .matrix-controls button, + :root[data-archive-theme]:not([data-theme]) .matrix-controls .layout-button:active, + :root[data-archive-theme]:not([data-theme]) .slide-button-secondary { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body); + border-color: var(--attack-border-color-body); + } + :root[data-archive-theme]:not([data-theme]) .btn-primary, + :root[data-archive-theme]:not([data-theme]) .footer .btn-primary { + color: var(--attack-on-color-active); + background-color: var(--attack-color-active); + border-color: var(--attack-color-active); + } + :root[data-archive-theme]:not([data-theme]) .btn-default:hover, + :root[data-archive-theme]:not([data-theme]) .btn-outline-secondary:not(:disabled, .disabled):hover, + :root[data-archive-theme]:not([data-theme]) .btn-outline-secondary:not(:disabled, .disabled):focus, + :root[data-archive-theme]:not([data-theme]) .matrix-controls button:hover, + :root[data-archive-theme]:not([data-theme]) .slide-button { + color: var(--attack-on-color-active); + background-color: var(--attack-color-active); + border-color: var(--attack-color-active); + } + :root[data-archive-theme]:not([data-theme]) .matrix.side .tactic .handle, + :root[data-archive-theme]:not([data-theme]) .matrix.flat .tactic .supertechnique td.sidebar.technique .handle { + color: var(--attack-color-body); + background-color: var(--attack-on-color-body-deemphasis); + } + :root[data-archive-theme]:not([data-theme]) .matrix.side .sidebar.expanded .angle, + :root[data-archive-theme]:not([data-theme]) .matrix.side .tactic .sidebar.expanded .angle { + background-color: var(--attack-color-body); + } + :root[data-archive-theme]:not([data-theme]) .matrix.side .tactic:hover:not(.name, .count), + :root[data-archive-theme]:not([data-theme]) .matrix.side .tactic:hover:not(.name, .count) .sidebar.expanded .angle { + background-color: var(--attack-background-color-body); + } + :root[data-archive-theme]:not([data-theme]) .matrix-container .scroll-indicator-group .scroll-indicator.right.show .cover { + background: linear-gradient(to right, rgba(255, 255, 255, 0.001), var(--attack-color-body)); + } + :root[data-archive-theme]:not([data-theme]) .matrix-container .scroll-indicator-group .scroll-indicator.left.show .cover { + background: linear-gradient(to left, rgba(255, 255, 255, 0.001), var(--attack-color-body)); + } + :root[data-archive-theme]:not([data-theme]) .matrix .tactic.count, + :root[data-archive-theme]:not([data-theme]) .matrix .technique-cell, + :root[data-archive-theme]:not([data-theme]) .resizer, + :root[data-archive-theme]:not([data-theme]) hr { + border-color: var(--attack-border-color-body); + } + :root[data-archive-theme]:not([data-theme]) .navbar-orange .nav-link, + :root[data-archive-theme]:not([data-theme]) .navbar .nav-tabs .nav-link, + :root[data-archive-theme]:not([data-theme]) .nav .dropdown-menu .dropdown-item { + color: white; + } + :root[data-archive-theme]:not([data-theme]) .nav .dropdown-menu { + background-color: var(--attack-color-primary); + } +} +@media print { + :root[data-archive-theme] .archive-theme-toggle { + display: none; + } +} + +/*# sourceMappingURL=style-archive.css.map */ diff --git a/attack-theme/static/style-attack.css b/attack-theme/static/style-attack.css index 5e75a1384d2..c75ec8b703f 100644 --- a/attack-theme/static/style-attack.css +++ b/attack-theme/static/style-attack.css @@ -37,18 +37,18 @@ src: url("fonts/Roboto/Roboto-Black.ttf"); } .deemphasis { - color: rgb(106.5, 114, 120.75); + color: var(--attack-on-color-body-deemphasis); } .matrix-header { - background-color: gray; - color: white; + background-color: var(--attack-color-matrix-header); + color: var(--attack-on-color-matrix-header); } .table-alternate, .blog-post table, .changelog table, .bg-alternate { - background-color: rgb(242.25, 242.25, 242.25) !important; + background-color: var(--attack-color-body-alternate) !important; } .text-label { @@ -58,7 +58,7 @@ .text-label-small { font-size: 12px; - color: #303435; + color: var(--attack-color-deemphasis); margin-top: -10px; } @@ -69,8 +69,8 @@ html { body { height: 100%; - background: white; - color: #39434c; + background: var(--attack-color-body); + color: var(--attack-on-color-body); font-family: "Roboto-Regular", sans-serif; display: flex; flex-direction: column; @@ -100,19 +100,22 @@ strong { .jumbotron { padding: 0; - background: white; - color: #39434c; + background: var(--attack-color-body); + color: var(--attack-on-color-body); } .jumbotron code { - color: rgb(28.5, 33.5, 38); - background-color: rgb(229.5, 229.5, 229.5); + color: var(--attack-on-color-body-emphasis); + background-color: var(--attack-color-body-alternate-strong); border-radius: 3px; font-family: courier, monospace; padding: 0 3px; } a { - color: #4f7cac; + color: var(--attack-color-link); +} +a:hover { + color: var(--attack-color-link-hover); } a .anchor::before { content: ""; @@ -175,7 +178,23 @@ a .anchor::before { padding: 0.3125rem 0; } .quick-links tr + tr { - border-top: 1px solid rgb(223.125, 223.125, 223.125); + border-top: 1px solid var(--attack-border-color-body); +} +.quick-links .external-link-icon { + margin-left: 0.125rem; + font-size: 0.8125rem; +} +.quick-links .random-page-toggle { + margin-left: 0.375rem; + padding: 0 0.375rem; + border: 1px solid var(--attack-color-active); + color: var(--attack-color-link); + background: var(--attack-color-body); +} +.quick-links .random-page-toggle:hover, .quick-links .random-page-toggle:focus, .quick-links .random-page-toggle[aria-expanded=true] { + border-color: var(--attack-color-secondary); + color: var(--attack-on-color-secondary); + background: var(--attack-color-secondary); } .row-main-page { @@ -191,16 +210,16 @@ a .anchor::before { } } .p-line p { - border-top: 0.0625rem solid #1c2226; + border-top: 0.0625rem solid var(--attack-border-color-body); } .btn-default { - background: white; - border-color: rgb(106.5, 114, 120.75); - color: #39434c; + background: var(--attack-color-body); + border-color: var(--attack-on-color-body-deemphasis); + color: var(--attack-on-color-body); } .btn-default:hover { - color: rgb(28.5, 33.5, 38); + color: var(--attack-on-color-body-emphasis); } .website-button, .slide-button-secondary, .slide-button { @@ -221,9 +240,9 @@ a .anchor::before { } .slide-button { - border-color: #0156b3; - color: #fff; - background: #0156b3; + border-color: var(--attack-color-active); + color: var(--attack-on-color-active); + background: var(--attack-color-active); padding: 6px 16px; } a .slide-button { @@ -231,9 +250,9 @@ a .slide-button { } .slide-button-secondary { - color: #0156b3; - background: #fff; - border-color: #0156b3; + color: var(--attack-color-active); + background: var(--attack-color-body); + border-color: var(--attack-color-active); padding: 6px 16px; } a .slide-button-secondary { @@ -249,15 +268,21 @@ a .slide-button-secondary { padding-left: 8px; } -.slide-button:hover { - background: #062f4f; - border-color: #062f4f; +.slide-button:hover, +.slide-button:focus, +.slide-button:active, +.slide-button[aria-expanded=true] { + background: var(--attack-color-secondary); + border-color: var(--attack-color-secondary); + color: var(--attack-on-color-secondary); } -.slide-button-secondary:hover { - background: #eaeaea; - border-color: #062f4f; - color: #062f4f; +.slide-button-secondary:hover, +.slide-button-secondary:focus, +.slide-button-secondary:active { + background: var(--attack-color-secondary); + border-color: var(--attack-color-secondary); + color: var(--attack-on-color-secondary); } .dropdown { @@ -268,7 +293,7 @@ a .slide-button-secondary { .dropdown-content { display: none; position: absolute; - background-color: white; + background-color: var(--attack-color-body); min-width: 160px; box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2); } @@ -278,16 +303,16 @@ a .slide-button-secondary { } .btn-navy:hover, .btn-navy { - color: white; - border-color: 0.0625rem solid #062f4f; + color: var(--attack-on-color-secondary); + border-color: 0.0625rem solid var(--attack-color-secondary); } .btn-navy { - background-color: #062f4f; - color: white; + background-color: var(--attack-color-secondary); + color: var(--attack-on-color-secondary); } .btn-navy:hover { - background-color: rgb(7.5, 58.75, 98.75); + background-color: var(--attack-color-secondary-hover); background-image: none; } @@ -312,17 +337,17 @@ a .slide-button-secondary { .changelog table { empty-cells: hide; } +.table td p:last-child, +.blog-post table td p:last-child, +.changelog table td p:last-child { + margin-bottom: 0; +} .table td, .blog-post table td, .changelog table td { padding: 0.75rem; vertical-align: top; } -.table td p:last-child, -.blog-post table td p:last-child, -.changelog table td p:last-child { - margin-bottom: 0; -} .table th, .blog-post table th, .changelog table th { @@ -333,7 +358,7 @@ a .slide-button-secondary { .table .active, .blog-post table .active, .changelog table .active { - color: #c63f1f; + color: var(--attack-color-active); } .blog-post table { @@ -343,13 +368,13 @@ a .slide-button-secondary { .table-alternate tbody, .blog-post table tbody, .changelog table tbody { - background: white; - color: #39434c; + background: var(--attack-color-body); + color: var(--attack-on-color-body); } .table-techniques thead tr { - background: #f2f2f2; - border-bottom: 2px solid #dee2e6; + background: var(--attack-color-body-alternate); + border-bottom: 2px solid var(--attack-border-color-body); } .table-techniques table { border-collapse: collapse; @@ -357,10 +382,10 @@ a .slide-button-secondary { .table-techniques td { vertical-align: top; padding: 10px; - border: 1px solid #dfdfdf; + border: 1px solid var(--attack-border-color-body); } .table-techniques tr:last-child { - border-bottom: 1px solid #dfdfdf; + border-bottom: 1px solid var(--attack-border-color-body); } .table-techniques .sub.technique { border-left: none; @@ -374,10 +399,10 @@ a .slide-button-secondary { border-bottom: none; } .table-techniques .sub.technique td:not(:nth-child(4)) { - color: #4f7cac; + color: var(--attack-color-link); } .table-techniques .technique:not(.sub) td:not(:nth-child(3)) { - color: #4f7cac; + color: var(--attack-color-link); } .techniques-used table { @@ -386,10 +411,10 @@ a .slide-button-secondary { .techniques-used td { vertical-align: top; padding: 10px; - border: 1px solid #dfdfdf; + border: 1px solid var(--attack-border-color-body); } .techniques-used tr:last-child { - border-bottom: 1px solid #dfdfdf; + border-bottom: 1px solid var(--attack-border-color-body); } .techniques-used .sub.technique { border-left: none; @@ -412,24 +437,24 @@ a .slide-button-secondary { } .techniques-used.background thead tr { - background: #f2f2f2; - border-bottom: 2px solid #dee2e6; + background: var(--attack-color-body-alternate); + border-bottom: 2px solid var(--attack-border-color-body); } .datasources-table table { border-collapse: collapse; } .datasources-table thead tr { - background: #f2f2f2; - border-bottom: 2px solid #dee2e6; + background: var(--attack-color-body-alternate); + border-bottom: 2px solid var(--attack-border-color-body); } .datasources-table td { vertical-align: top; padding: 10px; - border: 1px solid #dfdfdf; + border: 1px solid var(--attack-border-color-body); } .datasources-table tr:last-child { - border-bottom: 1px solid #dfdfdf; + border-bottom: 1px solid var(--attack-border-color-body); } .datasources-table .datacomponent.datasource { border-left: none; @@ -465,12 +490,12 @@ a .slide-button-secondary { .changelog table, .changelog table td, .changelog table th { - border: 1px solid rgb(223.125, 223.125, 223.125); + border: 1px solid var(--attack-border-color-body); } .table-bordered th, .blog-post table th, .changelog table th { - border-bottom: 2px solid rgb(223.125, 223.125, 223.125) !important; + border-bottom: 2px solid var(--attack-border-color-body) !important; } .table-matrix { @@ -479,7 +504,7 @@ a .slide-button-secondary { .table-matrix thead th { text-align: center !important; vertical-align: middle !important; - border: 0.0625rem solid rgb(223.125, 223.125, 223.125) !important; + border: 0.0625rem solid var(--attack-border-color-body) !important; } .table-matrix td, .table-matrix th { @@ -491,15 +516,15 @@ a .slide-button-secondary { } .table-matrix td.border, .table-matrix th.border { - border: 0.0625rem solid rgb(223.125, 223.125, 223.125) !important; + border: 0.0625rem solid var(--attack-border-color-body) !important; } .table-matrix td.no-border, .table-matrix th.no-border { border: none !important; } .table-matrix td { - background: white; - color: #39434c; + background: var(--attack-color-body); + color: var(--attack-on-color-body); } .table-mitigations th, @@ -519,18 +544,26 @@ a .slide-button-secondary { } .matrix-tactics-url { - color: white; + color: var(--attack-on-color-primary); } .matrix-tactics-url:visited, .matrix-tactics-url:hover, .matrix-tactics-url:active { - color: white; + color: var(--attack-on-color-primary); } /* **** */ /* BANNER */ .banner-message, .version-banner { - padding: 0.3125rem 0; + padding: 0.4375rem 1rem; + border-top: 1px solid var(--attack-border-color-banner); + border-bottom: 1px solid var(--attack-border-color-banner); text-align: center; - background-color: rgb(229.5, 229.5, 229.5); + color: var(--attack-on-color-banner); + background-color: var(--attack-color-banner); +} +.banner-message a, .version-banner a { + color: inherit; + font-weight: 700; + text-decoration: underline; } /* **** */ @@ -551,22 +584,22 @@ a .slide-button-secondary { /* **** */ /* Pre-block in SIGHTINGS */ pre { - color: #39434c; - background-color: rgb(216.75, 216.75, 216.75); + color: var(--attack-on-color-body); + background-color: var(--attack-color-body-alternate-strongest); border-radius: 5px; padding: 1rem; } code { - color: #c63e1f; + color: var(--attack-color-code); } /* **** */ /* CARDS */ .card { - background: white; - color: #39434c; - border-color: rgb(223.125, 223.125, 223.125); + background: var(--attack-color-body); + color: var(--attack-on-color-body); + border-color: var(--attack-border-color-body); } .button-group { @@ -588,7 +621,7 @@ code { width: 20%; top: 9.3rem; float: right; - background: #eaeaea; + background: var(--attack-color-body-alternate-strong); } @media screen and (width <= 90.62rem) { @@ -609,9 +642,9 @@ code { } .card-header { - color: #39434c; - background: rgba(57, 67, 76, 0.03); - border-bottom-color: rgb(223.125, 223.125, 223.125); + color: var(--attack-on-color-body); + background: var(--attack-color-card-header); + border-bottom-color: var(--attack-border-color-body); } a.partial-underline { @@ -643,21 +676,21 @@ a.partial-underline .hover-line { padding: 1.25rem 1.25rem 0; } .contact-card .card-header.background { - background: #0b2338; + background: var(--attack-color-footer); color: white; padding-bottom: 1.25rem; } .contact-card .card-header.no-background, .contact-card .card-footer.no-background { - background: white; - color: #39434c; + background: var(--attack-color-body); + color: var(--attack-on-color-body); border: unset; } .contact-card .card-body p { margin: 0; } .contact-card .card-body.background { - background: #eaeaea; + background: var(--attack-color-body-alternate-strong); } .card-body > .table { @@ -696,7 +729,11 @@ a.partial-underline .hover-line { .card-title { font-size: 1.1rem; - color: rgb(28.5, 33.5, 38); + color: var(--attack-on-color-body-emphasis); +} + +.card-data .card-title { + color: var(--attack-color-property-label); } .contact-card-title { @@ -706,7 +743,7 @@ a.partial-underline .hover-line { .card-title-icon { float: right; - color: #0156b3; + color: var(--attack-color-active); } /* **** */ @@ -716,13 +753,13 @@ a.partial-underline .hover-line { max-width: 100%; height: 30rem; margin: 0 auto; - border: 3px solid #dfdfdf; + border: 3px solid var(--attack-border-color-body); padding: 3px; display: flex; flex-direction: column; } .attack-box iframe { - border: 1px solid rgb(223.125, 223.125, 223.125) !important; + border: 1px solid var(--attack-border-color-body) !important; border-radius: 0.25rem; } @@ -730,13 +767,13 @@ a.partial-underline .hover-line { /* BREADCRUMBS */ .breadcrumb { font-size: 90%; - background-color: white; + background-color: var(--attack-color-body); max-width: 1140px; padding: 0 15px; } .breadcrumb .breadcrumb-item + .breadcrumb-item::before { content: ">"; - color: rgb(106.5, 114, 120.75); + color: var(--attack-on-color-body-deemphasis); } /* **** */ @@ -753,24 +790,24 @@ a.partial-underline .hover-line { counter-increment: item; } -.danger-card { - border-color: #c63f1f; -} .danger-card .card-header { - background: #c63f1f; - color: white; + background: var(--attack-color-primary); + color: var(--attack-on-color-primary); +} +.danger-card { + border-color: var(--attack-color-primary); } /* **** */ /* ATT&CKCON */ .bg-accord-light { - color: #39434c; - background-color: rgb(242.25, 242.25, 242.25); + color: var(--attack-on-color-body); + background-color: var(--attack-color-body-alternate); } .bg-accord-dark { - color: #39434c; - background-color: rgb(216.75, 216.75, 216.75); + color: var(--attack-on-color-body); + background-color: var(--attack-color-body-alternate-strongest); } /* **** */ @@ -786,9 +823,9 @@ a.partial-underline .hover-line { .usa-card__header { font-family: "Roboto-Regular", sans-serif; - color: white; - background: #062f4f; - border-bottom-color: rgb(223.125, 223.125, 223.125); + color: var(--attack-on-color-secondary); + background: var(--attack-color-secondary); + border-bottom-color: var(--attack-border-color-body); border-radius: 0.3rem 0.3rem 0 0; } @@ -835,7 +872,7 @@ a.partial-underline .hover-line { height: 500px; overflow: auto; padding: 0.9375rem; - background-color: rgb(242.25, 242.25, 242.25); + background-color: var(--attack-color-body-alternate); border-radius: 0.1875rem; } @@ -890,13 +927,13 @@ img.yt-core-image { } /* Ensure the sponsors block is below and not affected by the top image */ /* Card Blocks */ +.card-block .card-header h5 { + font-family: "Roboto-Regular", sans-serif; +} .card-block .card-header { display: flex; flex-direction: row; } -.card-block .card-header h5 { - font-family: "Roboto-Regular", sans-serif; -} .card-block .card-header :first-child { cursor: pointer; display: inline-block; @@ -911,7 +948,7 @@ img.yt-core-image { display: inline-block; vertical-align: top; background-position: center; - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%2339434C' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); + background-image: var(--attack-select-arrow); z-index: 1; transition: all 0.2s ease; } @@ -957,7 +994,7 @@ img.yt-core-image { } } .getting-started .getting-started-icon { - color: #c63f1f; + color: var(--attack-color-primary); } .getting-started .video-li { /* don't display the video in the list itself except in mobile layout */ @@ -992,7 +1029,7 @@ img.yt-core-image { padding-left: 0; } .timeline::before { - background: rgb(106.5, 114, 120.75); + background: var(--attack-on-color-body-deemphasis); position: absolute; width: 0.0625rem; height: 100%; @@ -1007,7 +1044,7 @@ img.yt-core-image { height: 300px; } .timeline:not(.show)::after { - background: linear-gradient(to bottom, transparent, white); + background: linear-gradient(to bottom, transparent, var(--attack-color-body)); position: absolute; width: 100%; height: 75px; @@ -1021,7 +1058,7 @@ img.yt-core-image { padding-left: 40px; } .timeline .timeline-card::before { - background: white; + background: var(--attack-color-body); position: absolute; width: 20px; height: 20px; @@ -1029,7 +1066,7 @@ img.yt-core-image { content: ""; display: inline-block; border-radius: 50%; - border: 0.125rem solid rgb(106.5, 114, 120.75); + border: 0.125rem solid var(--attack-on-color-body-deemphasis); left: 10px; } .timeline .timeline-card .timeline-card-header { @@ -1106,7 +1143,7 @@ img.yt-core-image { position: -webkit-sticky; position: sticky; top: 4.125rem; - background: white; + background: var(--attack-color-body); z-index: 100; margin-bottom: 0; } @@ -1129,7 +1166,7 @@ img.yt-core-image { } } .section-view .section-shadow { - border-bottom: 1px solid #dfdfdf !important; + border-bottom: 1px solid var(--attack-border-color-body) !important; } .section-view table { margin-top: -1px; @@ -1143,7 +1180,7 @@ div#sidebars { /* Plus/Minus expand icons */ .expand-icon { font-size: 5%; - color: #39434c; + color: var(--attack-on-color-body); margin-top: 0.4375rem; } @@ -1152,6 +1189,17 @@ div#sidebars { .attackcons { border-top-width: 0; } +.attackcons .attackcon-banner-image { + display: inline-block; + width: 100%; + box-sizing: border-box; +} +.attackcons .attackcon-banner-image.on-light { + padding: 1rem; + border: 1px solid var(--attack-border-color-body); + border-radius: 0.75rem; + background: var(--attack-color-image-background); +} .attackcons .sponsors { flex: 1; padding-left: 25px; @@ -1160,16 +1208,20 @@ div#sidebars { width: 90%; } .attackcons .sponsors-block { - background: #eaeaea; + background: var(--attack-color-image-background); text-align: center; display: flex; justify-content: space-evenly; flex-wrap: wrap; flex-direction: column; width: 200%; + padding: 0.625rem; + border: 0.0625rem solid var(--attack-border-color-body); + border-radius: 0.5rem; + box-sizing: border-box; } .attackcons .sponsors-block .img-container { - margin: 10px; + margin: 0.625rem; flex: 1 1 20%; box-sizing: border-box; } @@ -1186,7 +1238,7 @@ div#sidebars { } .support-box { - background-color: rgb(229.5, 229.5, 229.5); + background-color: var(--attack-color-body-alternate-strong); padding: 1.5em; border-radius: 0.75em; width: fit-content; @@ -1207,7 +1259,7 @@ div#sidebars { display: contents; } .sponsor-square img.sponsor-logo { - background-color: white; + background-color: var(--attack-color-image-background); border-radius: 6px; object-fit: contain; object-position: center; @@ -1216,7 +1268,7 @@ div#sidebars { /* **** */ /* training pages */ .training .exercise { - background: rgb(242.25, 242.25, 242.25); + background: var(--attack-color-body-alternate); } .card-training { @@ -1250,7 +1302,7 @@ div#sidebars { .resource { flex: 1; - background-color: #eaeaea; + background-color: var(--attack-color-body-alternate-strong); padding: 10px; box-sizing: border-box; } @@ -1281,14 +1333,14 @@ div#sidebars { margin-bottom: 16px; } .decorative-panels .decorative-panel .decorative-panel-body.show { - border-top: 1px solid rgb(223.125, 223.125, 223.125); + border-top: 1px solid var(--attack-border-color-body); } .decorative-panels .decorative-panel + .decorative-panel { margin-top: 25px; } .decorative-panels .decorative-panel:nth-of-type(even) { border-radius: 8px; - background: rgb(242.25, 242.25, 242.25); + background: var(--attack-color-body-alternate); } @media screen and (width <= 47.9875rem) { .decorative-panels .row { @@ -1315,16 +1367,16 @@ div#sidebars { object-fit: cover; object-position: left top; border-radius: unset !important; - border: 1px solid rgb(223.125, 223.125, 223.125); + border: 1px solid var(--attack-border-color-body); } .working-with-attack .panel { padding: 0; - border: 1px solid rgb(223.125, 223.125, 223.125); + border: 1px solid var(--attack-border-color-body); border-radius: 0.75em; max-width: 100%; } .working-with-attack img + .panel-body { - border-top: 1px solid rgb(223.125, 223.125, 223.125); + border-top: 1px solid var(--attack-border-color-body); } .working-with-attack .panel-body p { margin: 24px 0; @@ -1349,18 +1401,18 @@ div#sidebars { } .tip-box { - background: #eaeaea; + background: var(--attack-color-body-alternate-strong); padding: 1rem; } /* Card Blocks */ +.expand-panel .card-block .card-header h5 { + font-family: "Roboto-Regular", sans-serif; +} .expand-panel .card-block .card-header { display: flex; flex-direction: row; } -.expand-panel .card-block .card-header h5 { - font-family: "Roboto-Regular", sans-serif; -} .expand-panel .card-block .card-header :first-child { cursor: pointer; display: inline-block; @@ -1375,7 +1427,7 @@ div#sidebars { display: inline-block; vertical-align: top; background-position: center; - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%2339434C' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); + background-image: var(--attack-select-arrow); z-index: 1; transition: all 0.2s ease; } @@ -1412,8 +1464,8 @@ div#sidebars { } /* Footer styling */ .footer { - background-color: #0b2338; - color: white; + background-color: var(--attack-color-footer); + color: var(--attack-on-color-secondary); padding: 1rem 3rem; font-family: "Roboto-Thin", sans-serif; } @@ -1447,10 +1499,10 @@ div#sidebars { justify-content: center; } .footer .footer-link { - color: #87deff; + color: var(--attack-on-color-footer); } .footer .footer-link:hover { - color: rgb(107, 173, 213.5); + color: var(--attack-color-footer-link-hover); } .col .col-footer { @@ -1501,7 +1553,7 @@ div#sidebars { /* Top NAVIGATION */ .navbar { - background-color: #c63f1f; + background-color: var(--attack-color-primary); z-index: 1; } .navbar .navbar-brand { @@ -1511,31 +1563,31 @@ div#sidebars { border-bottom: none; } .navbar .nav-tabs .nav-link { - color: white; + color: var(--attack-on-color-primary); font-family: "Roboto-Light", sans-serif; } .navbar .nav-tabs .nav-link:focus { - color: white; + color: var(--attack-on-color-primary); } .navbar .nav-tabs .nav-link:hover:not(.active) { - color: white; + color: var(--attack-on-color-primary); background-color: transparent; } .navbar .nav-tabs .nav-link.active { - color: white; - background-color: #c63f1f; + color: var(--attack-on-color-primary); + background-color: var(--attack-color-primary); } .navbar .nav-tabs .nav-item.show .nav-link { - color: white; - background-color: #c63f1f; + color: var(--attack-on-color-primary); + background-color: var(--attack-color-primary); border-color: transparent; } .navbar .search-button { padding: 0.3rem 1rem; font-size: 1rem; - border: 0.0625rem solid white; + border: 0.0625rem solid var(--attack-on-color-primary); border-radius: 0.25rem; - color: white; + color: var(--attack-on-color-primary); line-height: 1.5rem; opacity: 0.8; } @@ -1555,21 +1607,118 @@ div#sidebars { } .navbar .search-button .search-icon { cursor: pointer; - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%23FFFFFF' xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%23fff' xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); } .navbar .search-button .error-icon { cursor: default; - background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg fill='%23FFFFFF' xmlns='http://www.w3.org/2000/svg' height='24' viewBox='0 96 960 960' width='24'%3e%3cpath d='M479.982 776q14.018 0 23.518-9.482 9.5-9.483 9.5-23.5 0-14.018-9.482-23.518-9.483-9.5-23.5-9.5-14.018 0-23.518 9.482-9.5 9.483-9.5 23.5 0 14.018 9.482 23.518 9.483 9.5 23.5 9.5ZM453 623h60V370h-60v253Zm27.266 353q-82.734 0-155.5-31.5t-127.266-86q-54.5-54.5-86-127.341Q80 658.319 80 575.5q0-82.819 31.5-155.659Q143 347 197.5 293t127.341-85.5Q397.681 176 480.5 176q82.819 0 155.659 31.5Q709 239 763 293t85.5 127Q880 493 880 575.734q0 82.734-31.5 155.5T763 858.316q-54 54.316-127 86Q563 976 480.266 976Zm.234-60Q622 916 721 816.5t99-241Q820 434 721.188 335 622.375 236 480 236q-141 0-240.5 98.812Q140 433.625 140 576q0 141 99.5 240.5t241 99.5Zm-.5-340Z'/%3e%3c/svg%3e"); + background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg fill='%23fff' xmlns='http://www.w3.org/2000/svg' height='24' viewBox='0 96 960 960' width='24'%3e%3cpath d='M479.982 776q14.018 0 23.518-9.482 9.5-9.483 9.5-23.5 0-14.018-9.482-23.518-9.483-9.5-23.5-9.5-14.018 0-23.518 9.482-9.5 9.483-9.5 23.5 0 14.018 9.482 23.518 9.483 9.5 23.5 9.5ZM453 623h60V370h-60v253Zm27.266 353q-82.734 0-155.5-31.5t-127.266-86q-54.5-54.5-86-127.341Q80 658.319 80 575.5q0-82.819 31.5-155.659Q143 347 197.5 293t127.341-85.5Q397.681 176 480.5 176q82.819 0 155.659 31.5Q709 239 763 293t85.5 127Q880 493 880 575.734q0 82.734-31.5 155.5T763 858.316q-54 54.316-127 86Q563 976 480.266 976Zm.234-60Q622 916 721 816.5t99-241Q820 434 721.188 335 622.375 236 480 236q-141 0-240.5 98.812Q140 433.625 140 576q0 141 99.5 240.5t241 99.5Zm-.5-340Z'/%3e%3c/svg%3e"); +} +.navbar .theme-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 3.625rem; + min-height: 2.375rem; + margin-right: 0.5rem; + padding: 0.25rem; + border: 0; + color: var(--attack-on-color-primary); +} +.navbar .theme-toggle .theme-toggle-track { + position: relative; + display: inline-flex; + align-items: center; + justify-content: space-between; + width: 3.125rem; + height: 1.75rem; + padding: 0 0.4375rem; + border: 0.0625rem solid var(--attack-on-color-primary); + border-radius: 0.875rem; + background: rgba(0, 0, 0, 0.2); + box-sizing: border-box; + transition: background-color 0.2s ease; +} +.navbar .theme-toggle .theme-toggle-icon { + position: relative; + z-index: 2; + visibility: visible; + display: inline-flex; + align-items: center; + justify-content: center; + width: 0.75rem; + height: 0.75rem; + font-size: 0.75rem; + line-height: 1; +} +.navbar .theme-toggle:hover, .navbar .theme-toggle:focus { + color: var(--attack-on-color-primary); +} +.navbar .theme-toggle:hover .theme-toggle-track, .navbar .theme-toggle:focus .theme-toggle-track { + background: rgba(0, 0, 0, 0.35); + box-shadow: 0 0 0 0.125rem rgba(255, 255, 255, 0.3); +} +.navbar .theme-toggle:focus { + outline: 0; + box-shadow: none; +} +.navbar .theme-toggle .theme-toggle-icon-light { + color: var(--attack-color-primary); +} +.navbar .theme-toggle .theme-toggle-thumb { + position: absolute; + top: 0.125rem; + left: 0.125rem; + z-index: 1; + width: 1.375rem; + height: 1.375rem; + border-radius: 50%; + background: var(--attack-on-color-primary); + box-shadow: 0 0.0625rem 0.1875rem rgba(0, 0, 0, 0.35); + transition: transform 0.2s ease; +} +.navbar .theme-toggle[data-theme-effective=dark] .theme-toggle-track { + background: var(--attack-color-secondary); +} +.navbar .theme-toggle[data-theme-effective=dark] .theme-toggle-icon-light { + color: var(--attack-on-color-primary); +} +.navbar .theme-toggle[data-theme-effective=dark] .theme-toggle-icon-dark { + color: var(--attack-color-secondary); +} +.navbar .theme-toggle[data-theme-effective=dark] .theme-toggle-thumb { + transform: translateX(1.375rem); +} +:root[data-theme=dark] .navbar .theme-toggle .theme-toggle-track { + background: var(--attack-color-secondary); +} +:root[data-theme=dark] .navbar .theme-toggle .theme-toggle-icon-light { + color: var(--attack-on-color-primary); +} +:root[data-theme=dark] .navbar .theme-toggle .theme-toggle-icon-dark { + color: var(--attack-color-secondary); +} +:root[data-theme=dark] .navbar .theme-toggle .theme-toggle-thumb { + transform: translateX(1.375rem); +} +@media (prefers-color-scheme: dark) { + :root:not([data-theme]) .navbar .theme-toggle .theme-toggle-track { + background: var(--attack-color-secondary); + } + :root:not([data-theme]) .navbar .theme-toggle .theme-toggle-icon-light { + color: var(--attack-on-color-primary); + } + :root:not([data-theme]) .navbar .theme-toggle .theme-toggle-icon-dark { + color: var(--attack-color-secondary); + } + :root:not([data-theme]) .navbar .theme-toggle .theme-toggle-thumb { + transform: translateX(1.375rem); + } } /* **** */ .nav, .faq { /* NAVIGATION Dropdown */ - /* **** */ - /* Side NAVIGATION */ - border-color: rgb(223.125, 223.125, 223.125) !important; - /* **** */ } .nav .dropdown:hover > .dropdown-menu, .faq .dropdown:hover > .dropdown-menu { @@ -1577,23 +1726,29 @@ div#sidebars { } .nav .dropdown-menu, .faq .dropdown-menu { - background-color: #c63f1f; + background-color: var(--attack-color-primary); } .nav .dropdown-menu .dropdown-item, .faq .dropdown-menu .dropdown-item { - color: white; + color: var(--attack-on-color-primary); } .nav .dropdown-menu .dropdown-item:hover, .nav .dropdown-menu .dropdown-item:focus, .faq .dropdown-menu .dropdown-item:hover, .faq .dropdown-menu .dropdown-item:focus { - color: white; + color: var(--attack-on-color-primary); text-decoration: underline; background-color: transparent; } +.nav, +.faq { + /* **** */ + /* Side NAVIGATION */ + border-color: var(--attack-border-color-body) !important; +} .nav .heading, .faq .heading { font-size: 1.6rem; - color: rgb(106.5, 114, 120.75); + color: var(--attack-on-color-body-deemphasis); letter-spacing: 0.1875rem; pointer-events: none; } @@ -1640,33 +1795,37 @@ div#sidebars { .nav .heading-dropdown, .faq .heading-dropdown { font-size: 1.2rem; - color: #062f4f; + color: var(--attack-color-secondary); letter-spacing: 0.1875rem; } @media screen and (width <= 90.62rem) { .nav .heading, .faq .heading { font-size: 1.2rem; - color: rgb(106.5, 114, 120.75); + color: var(--attack-on-color-body-deemphasis); letter-spacing: 0.1875rem; } .nav .heading-dropdown, .faq .heading-dropdown { font-size: 1rem; - color: #39434c; + color: var(--attack-on-color-body); letter-spacing: 0.0625rem; } } +.nav, +.faq { + /* **** */ +} .nav .nav-link, .faq .nav-link { font-size: 1rem; padding: 0.3rem 1rem; - color: rgb(106.5, 114, 120.75); + color: var(--attack-on-color-body-deemphasis); } .nav .nav-link.expand-title, .faq .nav-link.expand-title { font-size: 1.1rem; - color: #39434c; + color: var(--attack-on-color-body); } .nav .nav-link.side, .faq .nav-link.side { @@ -1675,14 +1834,14 @@ div#sidebars { } .nav .nav-link.side:hover, .faq .nav-link.side:hover { - background-color: #c63f1f; - color: white; + background-color: var(--attack-color-primary); + color: var(--attack-on-color-primary); } .nav .nav-link.side.active, .faq .nav-link.side.active { - color: #c63f1f; - background-color: rgb(242.25, 242.25, 242.25); - border-right: 0.1875rem solid #c63f1f; + color: var(--attack-color-primary); + background-color: var(--attack-color-body-alternate); + border-right: 0.1875rem solid var(--attack-color-primary); } /* **** */ @@ -1693,7 +1852,7 @@ div#sidebars { cursor: col-resize; height: 100%; position: absolute; - background-color: #dfdfdf; + background-color: var(--attack-border-color-body); } .data-sources-menu { @@ -1725,11 +1884,11 @@ div#sidebars { } } .sidebar.nav .sidenav-wrapper .heading { - border-bottom: 1px solid rgb(242.25, 242.25, 242.25); + border-bottom: 1px solid var(--attack-color-body-alternate); flex: 0 1 0; } .sidebar.nav .sidenav-wrapper .checkbox-div { - border-bottom: 1px solid rgb(242.25, 242.25, 242.25); + border-bottom: 1px solid var(--attack-color-body-alternate); flex: 0 1 0; } .sidebar.nav .sidenav-wrapper .sidenav-list { @@ -1748,11 +1907,11 @@ div#sidebars { .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head a, .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head .expand-button { cursor: pointer; - color: black; + color: var(--attack-on-color-body); } .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head a:hover, .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head .expand-button:hover { - background: rgb(242.25, 242.25, 242.25); + background: var(--attack-color-body-alternate); } .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head a, .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head span { @@ -1768,7 +1927,7 @@ div#sidebars { } .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head .expand-button { padding: 5px; - border-left: 1px solid rgb(242.25, 242.25, 242.25); + border-left: 1px solid var(--attack-color-body-alternate); display: inline-block; display: flex; flex-direction: row; @@ -1787,7 +1946,7 @@ div#sidebars { display: inline-block; vertical-align: top; background-position: center; - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%2339434C' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); + background-image: var(--attack-select-arrow); z-index: 1; transition: all 0.2s ease; } @@ -1795,9 +1954,9 @@ div#sidebars { transform: rotate(-180deg); } .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head.active, .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head.active > a { - color: #0156b3 !important; + color: var(--attack-color-active) !important; font-weight: bolder; - background: #eaeaea; + background: var(--attack-color-body-alternate-strong); font-family: Roboto-Bold, sans-serif; } .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-body { @@ -1834,8 +1993,8 @@ div#sidebars { } } .search-word-found { - background: yellow; - color: black; + background: var(--attack-color-search-highlight); + color: var(--attack-on-color-search-highlight); } .btn-group-text { @@ -1858,8 +2017,8 @@ div#sidebars { } .overlay.search .overlay-inner { border-radius: 25px; - background: white; - color: #39434c; + background: var(--attack-color-body); + color: var(--attack-on-color-body); width: 100%; height: 100%; display: flex; @@ -1880,8 +2039,8 @@ div#sidebars { line-height: 50px; width: 100%; border: 0; - background: white; - color: #39434c; + background: var(--attack-color-body); + color: var(--attack-on-color-body); } .overlay.search .overlay-inner .search-header .search-input input:focus { outline: none; @@ -1919,10 +2078,10 @@ div#sidebars { .overlay.search .overlay-inner .search-filters button, .overlay.search .overlay-inner .search-filters .search-filter-chip { min-height: 36px; - border: 1px solid rgb(223.125, 223.125, 223.125); + border: 1px solid var(--attack-border-color-body); border-radius: 4px; - background: white; - color: #39434c; + background: var(--attack-color-body); + color: var(--attack-on-color-body); cursor: pointer; } .overlay.search .overlay-inner .search-filters .search-filter-summary { @@ -1935,7 +2094,7 @@ div#sidebars { font-weight: 600; } .overlay.search .overlay-inner .search-filters .search-filter-summary-chip.open { - border-color: #0156b3; + border-color: var(--attack-color-active); } .overlay.search .overlay-inner .search-filters .search-filter-dropdown { position: relative; @@ -1949,9 +2108,9 @@ div#sidebars { min-width: 240px; max-width: min(420px, 100vw - 100px); padding: 12px; - border: 1px solid rgb(223.125, 223.125, 223.125); + border: 1px solid var(--attack-border-color-body); border-radius: 8px; - background: white; + background: var(--attack-color-body); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); } .overlay.search .overlay-inner .search-filters .search-filter-group-heading { @@ -1994,9 +2153,9 @@ div#sidebars { border: 0; } .overlay.search .overlay-inner .search-filters .search-filter-chip.selected { - border-color: #0156b3; - background: #0156b3; - color: #eaeaea; + border-color: var(--attack-color-active); + background: var(--attack-color-active); + color: var(--attack-on-color-active); } .overlay.search .overlay-inner .search-filters .search-filter-count { margin-left: 4px; @@ -2012,7 +2171,7 @@ div#sidebars { flex-direction: column; min-height: 0; padding: 0 50px; - border-top: 1px solid rgb(223.125, 223.125, 223.125); + border-top: 1px solid var(--attack-border-color-body); margin-bottom: 25px; overflow: hidden; scrollbar-gutter: stable; @@ -2037,17 +2196,18 @@ div#sidebars { padding: 2px 8px; border: 1px solid; border-radius: 4px; - color: white; + color: var(--attack-on-color-active); font-size: 0.8rem; font-weight: 700; } .overlay.search .overlay-inner .search-body .results .search-result-badge-page-type { - border-color: #0156b3; - background: rgb(19.975, 92.225, 171.275); + border-color: var(--attack-color-active); + background: var(--attack-color-active-alternate-medium); } .overlay.search .overlay-inner .search-body .results .search-result-badge-domain { - border-color: #303435; - background: #303435; + border-color: var(--attack-color-deemphasis); + background: var(--attack-color-deemphasis); + color: var(--attack-on-color-deemphasis); } .overlay.search .overlay-inner .search-body .results .search-no-results .preview { display: flex; @@ -2072,8 +2232,8 @@ div#sidebars { justify-content: flex-end; min-height: 58px; padding: 12px 0 14px; - border-top: 1px solid rgb(223.125, 223.125, 223.125); - background: white; + border-top: 1px solid var(--attack-border-color-body); + background: var(--attack-color-body); } .overlay.search .overlay-inner .search-body .search-results-pagination:empty { display: none; @@ -2088,17 +2248,17 @@ div#sidebars { .overlay.search .overlay-inner .search-body .search-pagination button { min-height: 36px; padding: 5px 12px; - border: 1px solid rgb(223.125, 223.125, 223.125); + border: 1px solid var(--attack-border-color-body); border-radius: 4px; - background: white; - color: #39434c; + background: var(--attack-color-body); + color: var(--attack-on-color-body); cursor: pointer; font-weight: 700; } .overlay.search .overlay-inner .search-body .search-pagination button.current { - border-color: #0156b3; - background: #0156b3; - color: #eaeaea; + border-color: var(--attack-color-active); + background: var(--attack-color-active); + color: var(--attack-on-color-active); } .overlay.search .overlay-inner .search-body .search-pagination button:disabled { cursor: default; @@ -2121,7 +2281,7 @@ div#sidebars { align-items: center; min-height: 36px; padding: 0 4px; - color: #303435; + color: var(--attack-color-deemphasis); font-weight: 700; } .overlay.search .overlay-inner .search-body .search-pagination-icon { @@ -2208,27 +2368,27 @@ div#sidebars { margin-bottom: 5px !important; } } -.popover { - box-shadow: 0 0 5px 3px white; - border-color: #c63f1f; -} .popover .popover-header { - background: #c63f1f; - color: white; + background: var(--attack-color-primary); + color: var(--attack-on-color-primary); +} +.popover { + box-shadow: 0 0 5px 3px var(--attack-color-body); + border-color: var(--attack-color-primary); } .popover.bs-popover-left .arrow::before { - border-left-color: #c63f1f; + border-left-color: var(--attack-color-primary); } .popover.bs-popover-right .arrow::before { - border-right-color: #c63f1f; + border-right-color: var(--attack-color-primary); } .popover.bs-popover-top .arrow::before { - border-top-color: #c63f1f; + border-top-color: var(--attack-color-primary); } .popover.bs-popover-bottom .arrow::before, .popover.bs-popover-bottom .arrow::after, .popover.bs-popover-bottom .popover-header::before { - border-bottom-color: #c63f1f; + border-bottom-color: var(--attack-color-primary); } .tour-backdrop, @@ -2237,21 +2397,21 @@ div#sidebars { } .matrix-container { - border: 1px solid rgb(223.125, 223.125, 223.125); - background: white; + border: 1px solid var(--attack-border-color-body); + background: var(--attack-color-body); } .matrix-container + .matrix-container { margin-top: 1rem; } .matrix-container .matrix-border { - border-left: 1px solid rgb(223.125, 223.125, 223.125); + border-left: 1px solid var(--attack-border-color-body); padding-left: 0.5rem; display: flex; justify-content: center; align-items: center; } .matrix-container .matrix-title { - border-bottom: 1px solid rgb(223.125, 223.125, 223.125); + border-bottom: 1px solid var(--attack-border-color-body); margin-bottom: 1rem; padding-bottom: 0.5rem; } @@ -2278,35 +2438,35 @@ div#sidebars { right: 0; } .matrix-container .scroll-indicator-group .scroll-indicator.right.show .cover { - background: linear-gradient(to right, rgba(255, 255, 255, 0.001), white); + background: linear-gradient(to right, rgba(255, 255, 255, 0.001), var(--attack-color-body)); } .matrix-container .scroll-indicator-group .scroll-indicator.left .cover { left: 0; } .matrix-container .scroll-indicator-group .scroll-indicator.left.show .cover { - background: linear-gradient(to left, rgba(255, 255, 255, 0.001), white); + background: linear-gradient(to left, rgba(255, 255, 255, 0.001), var(--attack-color-body)); } .matrix { white-space: normal; line-height: 14px; } -.matrix.side .tactic { - padding: 2px 5px; - width: 1%; - vertical-align: top; -} .matrix.side .tactic:first-child { padding: 2px 5px 2px 2px; } .matrix.side .tactic:last-child { padding: 2px 2px 2px 5px; } +.matrix.side .tactic { + padding: 2px 5px; + width: 1%; + vertical-align: top; +} .matrix.side .tactic:hover:not(.name, .count) { - background: rgb(223.125, 223.125, 223.125); + background: var(--attack-background-color-body); } .matrix.side .tactic:hover:not(.name, .count) .sidebar.expanded .angle { - background: rgb(223.125, 223.125, 223.125); + background: var(--attack-background-color-body); } .matrix.side .tactic.name, .matrix.side .tactic.count { text-align: center; @@ -2317,7 +2477,7 @@ div#sidebars { } .matrix.side .tactic.count { font-size: 13px; - border-bottom: 1px solid black; + border-bottom: 1px solid var(--attack-border-color-body); padding-bottom: 5px; margin-bottom: 5px; } @@ -2343,23 +2503,23 @@ div#sidebars { vertical-align: top; } .matrix.side .tactic .supertechnique td.technique { - outline: 1px solid rgb(106.5, 114, 120.75); + outline: 1px solid var(--attack-on-color-body-deemphasis); outline-offset: -1px; } +.matrix.side .tactic .subtechniques.hidden { + display: none; +} .matrix.side .tactic .subtechniques { display: flex; flex-direction: column; height: 100%; margin-left: -1px; - border-left: 2px solid rgb(106.5, 114, 120.75); - outline: 1px solid rgb(106.5, 114, 120.75); + border-left: 2px solid var(--attack-on-color-body-deemphasis); + outline: 1px solid var(--attack-on-color-body-deemphasis); outline-offset: -1px; white-space: nowrap; vertical-align: top; } -.matrix.side .tactic .subtechniques.hidden { - display: none; -} .matrix.side .tactic .subtechniques .subtechnique { height: 100%; flex-grow: 1; @@ -2368,7 +2528,7 @@ div#sidebars { text-align: center; vertical-align: middle; transform: rotate(-90deg); - color: rgb(242.25, 242.25, 242.25); + color: var(--attack-color-body-alternate); width: 12px; height: 12px; font-size: 16px; @@ -2378,7 +2538,7 @@ div#sidebars { min-width: 8px; width: 12px; padding: 0; - background: rgb(106.5, 114, 120.75); + background: var(--attack-on-color-body-deemphasis); cursor: pointer; position: relative; vertical-align: middle; @@ -2391,10 +2551,10 @@ div#sidebars { height: 12px; display: block; position: absolute; - background: white; + background: var(--attack-color-body); } .matrix.side .tactic .sidebar.expanded .angle svg { - fill: rgb(106.5, 114, 120.75); + fill: var(--attack-on-color-body-deemphasis); vertical-align: baseline; } .matrix.side .tactic .sidebar.expanded .angle.top { @@ -2417,7 +2577,7 @@ div#sidebars { } .matrix.flat .tactic.count { font-size: 13px; - border-bottom: 1px solid black; + border-bottom: 1px solid var(--attack-border-color-body); padding-bottom: 5px; margin-bottom: 5px; } @@ -2438,7 +2598,7 @@ div#sidebars { min-width: 8px; width: 12px; padding: 0; - background: rgb(106.5, 114, 120.75); + background: var(--attack-on-color-body-deemphasis); cursor: pointer; vertical-align: middle; } @@ -2446,21 +2606,21 @@ div#sidebars { text-align: center; vertical-align: middle; transform: rotate(-90deg); - color: rgb(242.25, 242.25, 242.25); + color: var(--attack-color-body-alternate); width: 12px; height: 9px; font-size: 16px; line-height: 12px; } .matrix.flat .tactic .supertechnique td.sidebar.subtechniques svg { - fill: rgb(106.5, 114, 120.75); + fill: var(--attack-on-color-body-deemphasis); vertical-align: baseline; } .matrix.flat .tactic .supertechnique td.sidebar { - border-right: 2px solid rgb(106.5, 114, 120.75); + border-right: 2px solid var(--attack-on-color-body-deemphasis); } .matrix.flat .tactic .supertechnique td.technique { - outline: 1px solid rgb(106.5, 114, 120.75); + outline: 1px solid var(--attack-on-color-body-deemphasis); outline-offset: -1px; } .matrix.flat .tactic .more-icon { @@ -2477,11 +2637,9 @@ div#sidebars { height: 100%; display: flex; align-items: center; - background-color: white; + background-color: var(--attack-color-body); font-size: 13px; line-height: 14px; - outline: 1px solid transparent; - outline-offset: -1px; } .matrix .technique-cell a { display: block; @@ -2489,8 +2647,12 @@ div#sidebars { height: 100%; padding: 7px 3px; } +.matrix .technique-cell { + outline: 1px solid transparent; + outline-offset: -1px; +} .matrix .technique-cell:not(.colored):not(.supertechniquecell) { - outline-color: rgb(223.125, 223.125, 223.125); + outline-color: var(--attack-border-color-body); } .matrix-controls { @@ -2499,17 +2661,17 @@ div#sidebars { padding: 1rem; } .matrix-controls button { - border-color: rgb(223.125, 223.125, 223.125); - background: white; - color: #39434c; + border-color: var(--attack-border-color-body); + background: var(--attack-color-body); + color: var(--attack-on-color-body); } .matrix-controls button:hover { - background: rgb(244.8, 244.8, 244.8); + background: var(--attack-color-body-alternate-subtle); } .matrix-controls .layout-button:active { - color: #16181b; + color: var(--attack-on-color-body); text-decoration: none; - background-color: #f8f9fa; + background-color: var(--attack-color-body-alternate-subtle); } .center-controls .matrix-controls .btn-toolbar { @@ -2528,9 +2690,275 @@ div#sidebars { } .version-table .table-break-row { - border-right-color: white; - border-left-color: white; + border-right-color: var(--attack-color-body); + border-left-color: var(--attack-color-body); padding: 1rem 0; } +:root, +:root[data-theme=light] { + --attack-color-primary: #c63f1f; + --attack-on-color-primary: white; + --attack-color-secondary: #062f4f; + --attack-color-secondary-hover: rgb(7.5, 58.75, 98.75); + --attack-on-color-secondary: white; + --attack-color-footer: #0b2338; + --attack-on-color-footer: #87deff; + --attack-color-footer-link-hover: rgb(99, 167, 206.5); + --attack-color-active: #0156b3; + --attack-color-active-alternate-medium: rgb(19.975, 92.225, 171.275); + --attack-on-color-active: #eaeaea; + --attack-color-body: white; + --attack-on-color-body: #39434c; + --attack-on-color-body-emphasis: #1d2226; + --attack-color-property-label: #1d2226; + --attack-on-color-body-deemphasis: #6b7379; + --attack-color-body-alternate-subtle: #f5f5f5; + --attack-color-body-alternate: #f2f2f2; + --attack-color-body-alternate-strong: #e6e6e6; + --attack-color-body-alternate-strongest: #d9d9d9; + --attack-border-color-body: #dfdfdf; + --attack-background-color-body: #dfdfdf; + --attack-color-link: #3f709e; + --attack-color-link-hover: #0056b3; + --attack-color-matrix-header: gray; + --attack-on-color-matrix-header: white; + --attack-color-search-highlight: yellow; + --attack-on-color-search-highlight: black; + --attack-color-deemphasis: #686f75; + --attack-on-color-deemphasis: white; + --attack-color-card-header: rgb(57 67 76 / 3%); + --attack-color-code: #a52f16; + --attack-color-danger: #bd2130; + --attack-color-image-background: white; + --attack-color-banner: #e7f0f6; + --attack-on-color-banner: #263b4a; + --attack-border-color-banner: #c2d5e2; + --attack-select-arrow: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%2339434c' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'/%3E%3C/svg%3E"); + color-scheme: light; +} + +@media (prefers-color-scheme: dark) { + :root:not([data-theme]) { + --attack-color-primary: #c63f1f; + --attack-on-color-primary: white; + --attack-color-secondary: #062f4f; + --attack-color-secondary-hover: rgb(8.4, 65.8, 110.6); + --attack-on-color-secondary: white; + --attack-color-footer: #0b2338; + --attack-on-color-footer: #87deff; + --attack-color-footer-link-hover: #b7edff; + --attack-color-active: #60a9ff; + --attack-color-active-alternate-medium: #3d8cdb; + --attack-on-color-active: #0f171c; + --attack-color-body: #222426; + --attack-on-color-body: #e8e6e3; + --attack-on-color-body-emphasis: #fffaf4; + --attack-color-property-label: #f2d2a4; + --attack-on-color-body-deemphasis: #b7b1a8; + --attack-color-body-alternate-subtle: #272a2c; + --attack-color-body-alternate: #2b2e30; + --attack-color-body-alternate-strong: #303437; + --attack-color-body-alternate-strongest: #353a3d; + --attack-border-color-body: #596166; + --attack-background-color-body: #373d40; + --attack-color-link: #7bb8ee; + --attack-color-link-hover: #b7ddff; + --attack-color-matrix-header: #596166; + --attack-on-color-matrix-header: #fffaf4; + --attack-color-search-highlight: #665a00; + --attack-on-color-search-highlight: #fff4b8; + --attack-color-deemphasis: #b7b1a8; + --attack-on-color-deemphasis: #222426; + --attack-color-card-header: #2b2e30; + --attack-color-code: #ff8f70; + --attack-color-danger: #ff8c96; + --attack-color-image-background: white; + --attack-color-banner: #263a49; + --attack-on-color-banner: #f2f7fa; + --attack-border-color-banner: #3f5d72; + --attack-select-arrow: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%23e8e6e3' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'/%3E%3C/svg%3E"); + color-scheme: dark; + } +} +:root[data-theme=dark] { + --attack-color-primary: #c63f1f; + --attack-on-color-primary: white; + --attack-color-secondary: #062f4f; + --attack-color-secondary-hover: rgb(8.4, 65.8, 110.6); + --attack-on-color-secondary: white; + --attack-color-footer: #0b2338; + --attack-on-color-footer: #87deff; + --attack-color-footer-link-hover: #b7edff; + --attack-color-active: #60a9ff; + --attack-color-active-alternate-medium: #3d8cdb; + --attack-on-color-active: #0f171c; + --attack-color-body: #222426; + --attack-on-color-body: #e8e6e3; + --attack-on-color-body-emphasis: #fffaf4; + --attack-color-property-label: #f2d2a4; + --attack-on-color-body-deemphasis: #b7b1a8; + --attack-color-body-alternate-subtle: #272a2c; + --attack-color-body-alternate: #2b2e30; + --attack-color-body-alternate-strong: #303437; + --attack-color-body-alternate-strongest: #353a3d; + --attack-border-color-body: #596166; + --attack-background-color-body: #373d40; + --attack-color-link: #7bb8ee; + --attack-color-link-hover: #b7ddff; + --attack-color-matrix-header: #596166; + --attack-on-color-matrix-header: #fffaf4; + --attack-color-search-highlight: #665a00; + --attack-on-color-search-highlight: #fff4b8; + --attack-color-deemphasis: #b7b1a8; + --attack-on-color-deemphasis: #222426; + --attack-color-card-header: #2b2e30; + --attack-color-code: #ff8f70; + --attack-color-danger: #ff8c96; + --attack-color-image-background: white; + --attack-color-banner: #263a49; + --attack-on-color-banner: #f2f7fa; + --attack-border-color-banner: #3f5d72; + --attack-select-arrow: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%23e8e6e3' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'/%3E%3C/svg%3E"); + color-scheme: dark; +} + +.form-control, +.custom-select, +.bootstrap-select > .dropdown-toggle, +.dropdown-menu, +.list-group-item, +.modal-content, +.popover, +.popover-body, +.page-link, +.input-group-text { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body); + border-color: var(--attack-border-color-body); +} + +.custom-select { + background-image: var(--attack-select-arrow); +} + +.form-control:focus, +.custom-select:focus { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body); + border-color: var(--attack-color-active); + box-shadow: 0 0 0 0.2rem rgba(76, 159, 254, 0.25); +} + +.dropdown-item, +.page-link { + color: var(--attack-color-link); +} + +.dropdown-item:hover, +.dropdown-item:focus, +.page-link:hover, +.page-link:focus { + color: var(--attack-on-color-body-emphasis); + background-color: var(--attack-color-body-alternate); +} + +.form-control:disabled, +.form-control[readonly], +.custom-select:disabled, +.page-item.disabled .page-link { + color: var(--attack-on-color-body-deemphasis); + background-color: var(--attack-color-body-alternate); +} + +.dropdown-divider, +hr { + border-color: var(--attack-border-color-body); +} + +.table { + color: var(--attack-on-color-body); +} + +.text-danger { + color: var(--attack-color-danger) !important; +} + +.btn-outline-secondary { + color: var(--attack-on-color-body); + border-color: var(--attack-on-color-body-deemphasis); +} +.btn-outline-secondary:disabled, .btn-outline-secondary.disabled { + color: var(--attack-on-color-body-deemphasis); + background-color: transparent; +} +.btn-outline-secondary:not(:disabled, .disabled):hover, .btn-outline-secondary:not(:disabled, .disabled):focus, .btn-outline-secondary:not(:disabled, .disabled):active { + color: var(--attack-on-color-active); + background-color: var(--attack-color-active); + border-color: var(--attack-color-active); +} + +.nav-tabs .nav-link { + color: var(--attack-color-link); + border-color: transparent; +} + +.nav-tabs .nav-link:hover, +.nav-tabs .nav-link:focus { + border-color: var(--attack-border-color-body); +} + +.nav-tabs .nav-link.active, +.nav-tabs .nav-item.show .nav-link { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body); + border-color: var(--attack-border-color-body) var(--attack-border-color-body) var(--attack-color-body); +} + +@media print { + :root, + :root[data-theme], + :root:not([data-theme]) { + --attack-color-primary: #c63f1f; + --attack-on-color-primary: white; + --attack-color-secondary: #062f4f; + --attack-color-secondary-hover: rgb(7.5, 58.75, 98.75); + --attack-on-color-secondary: white; + --attack-color-footer: #0b2338; + --attack-on-color-footer: #87deff; + --attack-color-footer-link-hover: rgb(99, 167, 206.5); + --attack-color-active: #0156b3; + --attack-color-active-alternate-medium: rgb(19.975, 92.225, 171.275); + --attack-on-color-active: #eaeaea; + --attack-color-body: white; + --attack-on-color-body: #39434c; + --attack-on-color-body-emphasis: #1d2226; + --attack-color-property-label: #1d2226; + --attack-on-color-body-deemphasis: #6b7379; + --attack-color-body-alternate-subtle: #f5f5f5; + --attack-color-body-alternate: #f2f2f2; + --attack-color-body-alternate-strong: #e6e6e6; + --attack-color-body-alternate-strongest: #d9d9d9; + --attack-border-color-body: #dfdfdf; + --attack-background-color-body: #dfdfdf; + --attack-color-link: #3f709e; + --attack-color-link-hover: #0056b3; + --attack-color-matrix-header: gray; + --attack-on-color-matrix-header: white; + --attack-color-search-highlight: yellow; + --attack-on-color-search-highlight: black; + --attack-color-deemphasis: #686f75; + --attack-on-color-deemphasis: white; + --attack-color-card-header: rgb(57 67 76 / 3%); + --attack-color-code: #a52f16; + --attack-color-danger: #bd2130; + --attack-color-image-background: white; + --attack-color-banner: #e7f0f6; + --attack-on-color-banner: #263b4a; + --attack-border-color-banner: #c2d5e2; + --attack-select-arrow: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%2339434c' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'/%3E%3C/svg%3E"); + color-scheme: light; + } +} + /*# sourceMappingURL=style-attack.css.map */ diff --git a/attack-theme/static/style-user.css b/attack-theme/static/style-user.css index 0f2df8b35c0..8ef288259e2 100644 --- a/attack-theme/static/style-user.css +++ b/attack-theme/static/style-user.css @@ -37,18 +37,18 @@ src: url("fonts/Roboto/Roboto-Black.ttf"); } .deemphasis { - color: rgb(106.5, 114, 120.75); + color: var(--attack-on-color-body-deemphasis); } .matrix-header { - background-color: gray; - color: white; + background-color: var(--attack-color-matrix-header); + color: var(--attack-on-color-matrix-header); } .table-alternate, .blog-post table, .changelog table, .bg-alternate { - background-color: rgb(242.25, 242.25, 242.25) !important; + background-color: var(--attack-color-body-alternate) !important; } .text-label { @@ -58,7 +58,7 @@ .text-label-small { font-size: 12px; - color: #303435; + color: var(--attack-color-deemphasis); margin-top: -10px; } @@ -69,8 +69,8 @@ html { body { height: 100%; - background: white; - color: #39434c; + background: var(--attack-color-body); + color: var(--attack-on-color-body); font-family: "Roboto-Regular", sans-serif; display: flex; flex-direction: column; @@ -100,19 +100,22 @@ strong { .jumbotron { padding: 0; - background: white; - color: #39434c; + background: var(--attack-color-body); + color: var(--attack-on-color-body); } .jumbotron code { - color: rgb(28.5, 33.5, 38); - background-color: rgb(229.5, 229.5, 229.5); + color: var(--attack-on-color-body-emphasis); + background-color: var(--attack-color-body-alternate-strong); border-radius: 3px; font-family: courier, monospace; padding: 0 3px; } a { - color: #4f7cac; + color: var(--attack-color-link); +} +a:hover { + color: var(--attack-color-link-hover); } a .anchor::before { content: ""; @@ -175,7 +178,23 @@ a .anchor::before { padding: 0.3125rem 0; } .quick-links tr + tr { - border-top: 1px solid rgb(223.125, 223.125, 223.125); + border-top: 1px solid var(--attack-border-color-body); +} +.quick-links .external-link-icon { + margin-left: 0.125rem; + font-size: 0.8125rem; +} +.quick-links .random-page-toggle { + margin-left: 0.375rem; + padding: 0 0.375rem; + border: 1px solid var(--attack-color-active); + color: var(--attack-color-link); + background: var(--attack-color-body); +} +.quick-links .random-page-toggle:hover, .quick-links .random-page-toggle:focus, .quick-links .random-page-toggle[aria-expanded=true] { + border-color: var(--attack-color-secondary); + color: var(--attack-on-color-secondary); + background: var(--attack-color-secondary); } .row-main-page { @@ -191,16 +210,16 @@ a .anchor::before { } } .p-line p { - border-top: 0.0625rem solid #1c2226; + border-top: 0.0625rem solid var(--attack-border-color-body); } .btn-default { - background: white; - border-color: rgb(106.5, 114, 120.75); - color: #39434c; + background: var(--attack-color-body); + border-color: var(--attack-on-color-body-deemphasis); + color: var(--attack-on-color-body); } .btn-default:hover { - color: rgb(28.5, 33.5, 38); + color: var(--attack-on-color-body-emphasis); } .website-button, .slide-button-secondary, .slide-button { @@ -221,9 +240,9 @@ a .anchor::before { } .slide-button { - border-color: #303435; - color: #fff; - background: #303435; + border-color: var(--attack-color-active); + color: var(--attack-on-color-active); + background: var(--attack-color-active); padding: 6px 16px; } a .slide-button { @@ -231,9 +250,9 @@ a .slide-button { } .slide-button-secondary { - color: #303435; - background: #fff; - border-color: #303435; + color: var(--attack-color-active); + background: var(--attack-color-body); + border-color: var(--attack-color-active); padding: 6px 16px; } a .slide-button-secondary { @@ -249,15 +268,21 @@ a .slide-button-secondary { padding-left: 8px; } -.slide-button:hover { - background: #303435; - border-color: #303435; +.slide-button:hover, +.slide-button:focus, +.slide-button:active, +.slide-button[aria-expanded=true] { + background: var(--attack-color-secondary); + border-color: var(--attack-color-secondary); + color: var(--attack-on-color-secondary); } -.slide-button-secondary:hover { - background: #eaeaea; - border-color: #303435; - color: #303435; +.slide-button-secondary:hover, +.slide-button-secondary:focus, +.slide-button-secondary:active { + background: var(--attack-color-secondary); + border-color: var(--attack-color-secondary); + color: var(--attack-on-color-secondary); } .dropdown { @@ -268,7 +293,7 @@ a .slide-button-secondary { .dropdown-content { display: none; position: absolute; - background-color: white; + background-color: var(--attack-color-body); min-width: 160px; box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2); } @@ -278,16 +303,16 @@ a .slide-button-secondary { } .btn-navy:hover, .btn-navy { - color: white; - border-color: 0.0625rem solid #303435; + color: var(--attack-on-color-secondary); + border-color: 0.0625rem solid var(--attack-color-secondary); } .btn-navy { - background-color: #303435; - color: white; + background-color: var(--attack-color-secondary); + color: var(--attack-on-color-secondary); } .btn-navy:hover { - background-color: rgb(57.7188118812, 62.5287128713, 63.7311881188); + background-color: var(--attack-color-secondary-hover); background-image: none; } @@ -312,17 +337,17 @@ a .slide-button-secondary { .changelog table { empty-cells: hide; } +.table td p:last-child, +.blog-post table td p:last-child, +.changelog table td p:last-child { + margin-bottom: 0; +} .table td, .blog-post table td, .changelog table td { padding: 0.75rem; vertical-align: top; } -.table td p:last-child, -.blog-post table td p:last-child, -.changelog table td p:last-child { - margin-bottom: 0; -} .table th, .blog-post table th, .changelog table th { @@ -333,7 +358,7 @@ a .slide-button-secondary { .table .active, .blog-post table .active, .changelog table .active { - color: #303435; + color: var(--attack-color-active); } .blog-post table { @@ -343,13 +368,13 @@ a .slide-button-secondary { .table-alternate tbody, .blog-post table tbody, .changelog table tbody { - background: white; - color: #39434c; + background: var(--attack-color-body); + color: var(--attack-on-color-body); } .table-techniques thead tr { - background: #f2f2f2; - border-bottom: 2px solid #dee2e6; + background: var(--attack-color-body-alternate); + border-bottom: 2px solid var(--attack-border-color-body); } .table-techniques table { border-collapse: collapse; @@ -357,10 +382,10 @@ a .slide-button-secondary { .table-techniques td { vertical-align: top; padding: 10px; - border: 1px solid #dfdfdf; + border: 1px solid var(--attack-border-color-body); } .table-techniques tr:last-child { - border-bottom: 1px solid #dfdfdf; + border-bottom: 1px solid var(--attack-border-color-body); } .table-techniques .sub.technique { border-left: none; @@ -374,10 +399,10 @@ a .slide-button-secondary { border-bottom: none; } .table-techniques .sub.technique td:not(:nth-child(4)) { - color: #4f7cac; + color: var(--attack-color-link); } .table-techniques .technique:not(.sub) td:not(:nth-child(3)) { - color: #4f7cac; + color: var(--attack-color-link); } .techniques-used table { @@ -386,10 +411,10 @@ a .slide-button-secondary { .techniques-used td { vertical-align: top; padding: 10px; - border: 1px solid #dfdfdf; + border: 1px solid var(--attack-border-color-body); } .techniques-used tr:last-child { - border-bottom: 1px solid #dfdfdf; + border-bottom: 1px solid var(--attack-border-color-body); } .techniques-used .sub.technique { border-left: none; @@ -412,24 +437,24 @@ a .slide-button-secondary { } .techniques-used.background thead tr { - background: #f2f2f2; - border-bottom: 2px solid #dee2e6; + background: var(--attack-color-body-alternate); + border-bottom: 2px solid var(--attack-border-color-body); } .datasources-table table { border-collapse: collapse; } .datasources-table thead tr { - background: #f2f2f2; - border-bottom: 2px solid #dee2e6; + background: var(--attack-color-body-alternate); + border-bottom: 2px solid var(--attack-border-color-body); } .datasources-table td { vertical-align: top; padding: 10px; - border: 1px solid #dfdfdf; + border: 1px solid var(--attack-border-color-body); } .datasources-table tr:last-child { - border-bottom: 1px solid #dfdfdf; + border-bottom: 1px solid var(--attack-border-color-body); } .datasources-table .datacomponent.datasource { border-left: none; @@ -465,12 +490,12 @@ a .slide-button-secondary { .changelog table, .changelog table td, .changelog table th { - border: 1px solid rgb(223.125, 223.125, 223.125); + border: 1px solid var(--attack-border-color-body); } .table-bordered th, .blog-post table th, .changelog table th { - border-bottom: 2px solid rgb(223.125, 223.125, 223.125) !important; + border-bottom: 2px solid var(--attack-border-color-body) !important; } .table-matrix { @@ -479,7 +504,7 @@ a .slide-button-secondary { .table-matrix thead th { text-align: center !important; vertical-align: middle !important; - border: 0.0625rem solid rgb(223.125, 223.125, 223.125) !important; + border: 0.0625rem solid var(--attack-border-color-body) !important; } .table-matrix td, .table-matrix th { @@ -491,15 +516,15 @@ a .slide-button-secondary { } .table-matrix td.border, .table-matrix th.border { - border: 0.0625rem solid rgb(223.125, 223.125, 223.125) !important; + border: 0.0625rem solid var(--attack-border-color-body) !important; } .table-matrix td.no-border, .table-matrix th.no-border { border: none !important; } .table-matrix td { - background: white; - color: #39434c; + background: var(--attack-color-body); + color: var(--attack-on-color-body); } .table-mitigations th, @@ -519,18 +544,26 @@ a .slide-button-secondary { } .matrix-tactics-url { - color: white; + color: var(--attack-on-color-primary); } .matrix-tactics-url:visited, .matrix-tactics-url:hover, .matrix-tactics-url:active { - color: white; + color: var(--attack-on-color-primary); } /* **** */ /* BANNER */ .banner-message, .version-banner { - padding: 0.3125rem 0; + padding: 0.4375rem 1rem; + border-top: 1px solid var(--attack-border-color-banner); + border-bottom: 1px solid var(--attack-border-color-banner); text-align: center; - background-color: rgb(229.5, 229.5, 229.5); + color: var(--attack-on-color-banner); + background-color: var(--attack-color-banner); +} +.banner-message a, .version-banner a { + color: inherit; + font-weight: 700; + text-decoration: underline; } /* **** */ @@ -551,22 +584,22 @@ a .slide-button-secondary { /* **** */ /* Pre-block in SIGHTINGS */ pre { - color: #39434c; - background-color: rgb(216.75, 216.75, 216.75); + color: var(--attack-on-color-body); + background-color: var(--attack-color-body-alternate-strongest); border-radius: 5px; padding: 1rem; } code { - color: #c63e1f; + color: var(--attack-color-code); } /* **** */ /* CARDS */ .card { - background: white; - color: #39434c; - border-color: rgb(223.125, 223.125, 223.125); + background: var(--attack-color-body); + color: var(--attack-on-color-body); + border-color: var(--attack-border-color-body); } .button-group { @@ -588,7 +621,7 @@ code { width: 20%; top: 9.3rem; float: right; - background: #eaeaea; + background: var(--attack-color-body-alternate-strong); } @media screen and (width <= 90.62rem) { @@ -609,9 +642,9 @@ code { } .card-header { - color: #39434c; - background: rgba(57, 67, 76, 0.03); - border-bottom-color: rgb(223.125, 223.125, 223.125); + color: var(--attack-on-color-body); + background: var(--attack-color-card-header); + border-bottom-color: var(--attack-border-color-body); } a.partial-underline { @@ -643,21 +676,21 @@ a.partial-underline .hover-line { padding: 1.25rem 1.25rem 0; } .contact-card .card-header.background { - background: #303435; + background: var(--attack-color-footer); color: white; padding-bottom: 1.25rem; } .contact-card .card-header.no-background, .contact-card .card-footer.no-background { - background: white; - color: #39434c; + background: var(--attack-color-body); + color: var(--attack-on-color-body); border: unset; } .contact-card .card-body p { margin: 0; } .contact-card .card-body.background { - background: #eaeaea; + background: var(--attack-color-body-alternate-strong); } .card-body > .table { @@ -696,7 +729,11 @@ a.partial-underline .hover-line { .card-title { font-size: 1.1rem; - color: rgb(28.5, 33.5, 38); + color: var(--attack-on-color-body-emphasis); +} + +.card-data .card-title { + color: var(--attack-color-property-label); } .contact-card-title { @@ -706,7 +743,7 @@ a.partial-underline .hover-line { .card-title-icon { float: right; - color: #303435; + color: var(--attack-color-active); } /* **** */ @@ -716,13 +753,13 @@ a.partial-underline .hover-line { max-width: 100%; height: 30rem; margin: 0 auto; - border: 3px solid #dfdfdf; + border: 3px solid var(--attack-border-color-body); padding: 3px; display: flex; flex-direction: column; } .attack-box iframe { - border: 1px solid rgb(223.125, 223.125, 223.125) !important; + border: 1px solid var(--attack-border-color-body) !important; border-radius: 0.25rem; } @@ -730,13 +767,13 @@ a.partial-underline .hover-line { /* BREADCRUMBS */ .breadcrumb { font-size: 90%; - background-color: white; + background-color: var(--attack-color-body); max-width: 1140px; padding: 0 15px; } .breadcrumb .breadcrumb-item + .breadcrumb-item::before { content: ">"; - color: rgb(106.5, 114, 120.75); + color: var(--attack-on-color-body-deemphasis); } /* **** */ @@ -753,24 +790,24 @@ a.partial-underline .hover-line { counter-increment: item; } -.danger-card { - border-color: #303435; -} .danger-card .card-header { - background: #303435; - color: white; + background: var(--attack-color-primary); + color: var(--attack-on-color-primary); +} +.danger-card { + border-color: var(--attack-color-primary); } /* **** */ /* ATT&CKCON */ .bg-accord-light { - color: #39434c; - background-color: rgb(242.25, 242.25, 242.25); + color: var(--attack-on-color-body); + background-color: var(--attack-color-body-alternate); } .bg-accord-dark { - color: #39434c; - background-color: rgb(216.75, 216.75, 216.75); + color: var(--attack-on-color-body); + background-color: var(--attack-color-body-alternate-strongest); } /* **** */ @@ -786,9 +823,9 @@ a.partial-underline .hover-line { .usa-card__header { font-family: "Roboto-Regular", sans-serif; - color: white; - background: #303435; - border-bottom-color: rgb(223.125, 223.125, 223.125); + color: var(--attack-on-color-secondary); + background: var(--attack-color-secondary); + border-bottom-color: var(--attack-border-color-body); border-radius: 0.3rem 0.3rem 0 0; } @@ -835,7 +872,7 @@ a.partial-underline .hover-line { height: 500px; overflow: auto; padding: 0.9375rem; - background-color: rgb(242.25, 242.25, 242.25); + background-color: var(--attack-color-body-alternate); border-radius: 0.1875rem; } @@ -890,13 +927,13 @@ img.yt-core-image { } /* Ensure the sponsors block is below and not affected by the top image */ /* Card Blocks */ +.card-block .card-header h5 { + font-family: "Roboto-Regular", sans-serif; +} .card-block .card-header { display: flex; flex-direction: row; } -.card-block .card-header h5 { - font-family: "Roboto-Regular", sans-serif; -} .card-block .card-header :first-child { cursor: pointer; display: inline-block; @@ -911,7 +948,7 @@ img.yt-core-image { display: inline-block; vertical-align: top; background-position: center; - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%2339434C' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); + background-image: var(--attack-select-arrow); z-index: 1; transition: all 0.2s ease; } @@ -957,7 +994,7 @@ img.yt-core-image { } } .getting-started .getting-started-icon { - color: #303435; + color: var(--attack-color-primary); } .getting-started .video-li { /* don't display the video in the list itself except in mobile layout */ @@ -992,7 +1029,7 @@ img.yt-core-image { padding-left: 0; } .timeline::before { - background: rgb(106.5, 114, 120.75); + background: var(--attack-on-color-body-deemphasis); position: absolute; width: 0.0625rem; height: 100%; @@ -1007,7 +1044,7 @@ img.yt-core-image { height: 300px; } .timeline:not(.show)::after { - background: linear-gradient(to bottom, transparent, white); + background: linear-gradient(to bottom, transparent, var(--attack-color-body)); position: absolute; width: 100%; height: 75px; @@ -1021,7 +1058,7 @@ img.yt-core-image { padding-left: 40px; } .timeline .timeline-card::before { - background: white; + background: var(--attack-color-body); position: absolute; width: 20px; height: 20px; @@ -1029,7 +1066,7 @@ img.yt-core-image { content: ""; display: inline-block; border-radius: 50%; - border: 0.125rem solid rgb(106.5, 114, 120.75); + border: 0.125rem solid var(--attack-on-color-body-deemphasis); left: 10px; } .timeline .timeline-card .timeline-card-header { @@ -1106,7 +1143,7 @@ img.yt-core-image { position: -webkit-sticky; position: sticky; top: 4.125rem; - background: white; + background: var(--attack-color-body); z-index: 100; margin-bottom: 0; } @@ -1129,7 +1166,7 @@ img.yt-core-image { } } .section-view .section-shadow { - border-bottom: 1px solid #dfdfdf !important; + border-bottom: 1px solid var(--attack-border-color-body) !important; } .section-view table { margin-top: -1px; @@ -1143,7 +1180,7 @@ div#sidebars { /* Plus/Minus expand icons */ .expand-icon { font-size: 5%; - color: #39434c; + color: var(--attack-on-color-body); margin-top: 0.4375rem; } @@ -1152,6 +1189,17 @@ div#sidebars { .attackcons { border-top-width: 0; } +.attackcons .attackcon-banner-image { + display: inline-block; + width: 100%; + box-sizing: border-box; +} +.attackcons .attackcon-banner-image.on-light { + padding: 1rem; + border: 1px solid var(--attack-border-color-body); + border-radius: 0.75rem; + background: var(--attack-color-image-background); +} .attackcons .sponsors { flex: 1; padding-left: 25px; @@ -1160,16 +1208,20 @@ div#sidebars { width: 90%; } .attackcons .sponsors-block { - background: #eaeaea; + background: var(--attack-color-image-background); text-align: center; display: flex; justify-content: space-evenly; flex-wrap: wrap; flex-direction: column; width: 200%; + padding: 0.625rem; + border: 0.0625rem solid var(--attack-border-color-body); + border-radius: 0.5rem; + box-sizing: border-box; } .attackcons .sponsors-block .img-container { - margin: 10px; + margin: 0.625rem; flex: 1 1 20%; box-sizing: border-box; } @@ -1186,7 +1238,7 @@ div#sidebars { } .support-box { - background-color: rgb(229.5, 229.5, 229.5); + background-color: var(--attack-color-body-alternate-strong); padding: 1.5em; border-radius: 0.75em; width: fit-content; @@ -1207,7 +1259,7 @@ div#sidebars { display: contents; } .sponsor-square img.sponsor-logo { - background-color: white; + background-color: var(--attack-color-image-background); border-radius: 6px; object-fit: contain; object-position: center; @@ -1216,7 +1268,7 @@ div#sidebars { /* **** */ /* training pages */ .training .exercise { - background: rgb(242.25, 242.25, 242.25); + background: var(--attack-color-body-alternate); } .card-training { @@ -1250,7 +1302,7 @@ div#sidebars { .resource { flex: 1; - background-color: #eaeaea; + background-color: var(--attack-color-body-alternate-strong); padding: 10px; box-sizing: border-box; } @@ -1281,14 +1333,14 @@ div#sidebars { margin-bottom: 16px; } .decorative-panels .decorative-panel .decorative-panel-body.show { - border-top: 1px solid rgb(223.125, 223.125, 223.125); + border-top: 1px solid var(--attack-border-color-body); } .decorative-panels .decorative-panel + .decorative-panel { margin-top: 25px; } .decorative-panels .decorative-panel:nth-of-type(even) { border-radius: 8px; - background: rgb(242.25, 242.25, 242.25); + background: var(--attack-color-body-alternate); } @media screen and (width <= 47.9875rem) { .decorative-panels .row { @@ -1315,16 +1367,16 @@ div#sidebars { object-fit: cover; object-position: left top; border-radius: unset !important; - border: 1px solid rgb(223.125, 223.125, 223.125); + border: 1px solid var(--attack-border-color-body); } .working-with-attack .panel { padding: 0; - border: 1px solid rgb(223.125, 223.125, 223.125); + border: 1px solid var(--attack-border-color-body); border-radius: 0.75em; max-width: 100%; } .working-with-attack img + .panel-body { - border-top: 1px solid rgb(223.125, 223.125, 223.125); + border-top: 1px solid var(--attack-border-color-body); } .working-with-attack .panel-body p { margin: 24px 0; @@ -1349,18 +1401,18 @@ div#sidebars { } .tip-box { - background: #eaeaea; + background: var(--attack-color-body-alternate-strong); padding: 1rem; } /* Card Blocks */ +.expand-panel .card-block .card-header h5 { + font-family: "Roboto-Regular", sans-serif; +} .expand-panel .card-block .card-header { display: flex; flex-direction: row; } -.expand-panel .card-block .card-header h5 { - font-family: "Roboto-Regular", sans-serif; -} .expand-panel .card-block .card-header :first-child { cursor: pointer; display: inline-block; @@ -1375,7 +1427,7 @@ div#sidebars { display: inline-block; vertical-align: top; background-position: center; - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%2339434C' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); + background-image: var(--attack-select-arrow); z-index: 1; transition: all 0.2s ease; } @@ -1412,8 +1464,8 @@ div#sidebars { } /* Footer styling */ .footer { - background-color: #303435; - color: white; + background-color: var(--attack-color-footer); + color: var(--attack-on-color-secondary); padding: 1rem 3rem; font-family: "Roboto-Thin", sans-serif; } @@ -1447,10 +1499,10 @@ div#sidebars { justify-content: center; } .footer .footer-link { - color: #87deff; + color: var(--attack-on-color-footer); } .footer .footer-link:hover { - color: rgb(107, 173, 213.5); + color: var(--attack-color-footer-link-hover); } .col .col-footer { @@ -1501,7 +1553,7 @@ div#sidebars { /* Top NAVIGATION */ .navbar { - background-color: #303435; + background-color: var(--attack-color-primary); z-index: 1; } .navbar .navbar-brand { @@ -1511,31 +1563,31 @@ div#sidebars { border-bottom: none; } .navbar .nav-tabs .nav-link { - color: white; + color: var(--attack-on-color-primary); font-family: "Roboto-Light", sans-serif; } .navbar .nav-tabs .nav-link:focus { - color: white; + color: var(--attack-on-color-primary); } .navbar .nav-tabs .nav-link:hover:not(.active) { - color: white; + color: var(--attack-on-color-primary); background-color: transparent; } .navbar .nav-tabs .nav-link.active { - color: white; - background-color: #303435; + color: var(--attack-on-color-primary); + background-color: var(--attack-color-primary); } .navbar .nav-tabs .nav-item.show .nav-link { - color: white; - background-color: #303435; + color: var(--attack-on-color-primary); + background-color: var(--attack-color-primary); border-color: transparent; } .navbar .search-button { padding: 0.3rem 1rem; font-size: 1rem; - border: 0.0625rem solid white; + border: 0.0625rem solid var(--attack-on-color-primary); border-radius: 0.25rem; - color: white; + color: var(--attack-on-color-primary); line-height: 1.5rem; opacity: 0.8; } @@ -1555,21 +1607,118 @@ div#sidebars { } .navbar .search-button .search-icon { cursor: pointer; - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%23FFFFFF' xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%23fff' xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); } .navbar .search-button .error-icon { cursor: default; - background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg fill='%23FFFFFF' xmlns='http://www.w3.org/2000/svg' height='24' viewBox='0 96 960 960' width='24'%3e%3cpath d='M479.982 776q14.018 0 23.518-9.482 9.5-9.483 9.5-23.5 0-14.018-9.482-23.518-9.483-9.5-23.5-9.5-14.018 0-23.518 9.482-9.5 9.483-9.5 23.5 0 14.018 9.482 23.518 9.483 9.5 23.5 9.5ZM453 623h60V370h-60v253Zm27.266 353q-82.734 0-155.5-31.5t-127.266-86q-54.5-54.5-86-127.341Q80 658.319 80 575.5q0-82.819 31.5-155.659Q143 347 197.5 293t127.341-85.5Q397.681 176 480.5 176q82.819 0 155.659 31.5Q709 239 763 293t85.5 127Q880 493 880 575.734q0 82.734-31.5 155.5T763 858.316q-54 54.316-127 86Q563 976 480.266 976Zm.234-60Q622 916 721 816.5t99-241Q820 434 721.188 335 622.375 236 480 236q-141 0-240.5 98.812Q140 433.625 140 576q0 141 99.5 240.5t241 99.5Zm-.5-340Z'/%3e%3c/svg%3e"); + background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg fill='%23fff' xmlns='http://www.w3.org/2000/svg' height='24' viewBox='0 96 960 960' width='24'%3e%3cpath d='M479.982 776q14.018 0 23.518-9.482 9.5-9.483 9.5-23.5 0-14.018-9.482-23.518-9.483-9.5-23.5-9.5-14.018 0-23.518 9.482-9.5 9.483-9.5 23.5 0 14.018 9.482 23.518 9.483 9.5 23.5 9.5ZM453 623h60V370h-60v253Zm27.266 353q-82.734 0-155.5-31.5t-127.266-86q-54.5-54.5-86-127.341Q80 658.319 80 575.5q0-82.819 31.5-155.659Q143 347 197.5 293t127.341-85.5Q397.681 176 480.5 176q82.819 0 155.659 31.5Q709 239 763 293t85.5 127Q880 493 880 575.734q0 82.734-31.5 155.5T763 858.316q-54 54.316-127 86Q563 976 480.266 976Zm.234-60Q622 916 721 816.5t99-241Q820 434 721.188 335 622.375 236 480 236q-141 0-240.5 98.812Q140 433.625 140 576q0 141 99.5 240.5t241 99.5Zm-.5-340Z'/%3e%3c/svg%3e"); +} +.navbar .theme-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 3.625rem; + min-height: 2.375rem; + margin-right: 0.5rem; + padding: 0.25rem; + border: 0; + color: var(--attack-on-color-primary); +} +.navbar .theme-toggle .theme-toggle-track { + position: relative; + display: inline-flex; + align-items: center; + justify-content: space-between; + width: 3.125rem; + height: 1.75rem; + padding: 0 0.4375rem; + border: 0.0625rem solid var(--attack-on-color-primary); + border-radius: 0.875rem; + background: rgba(0, 0, 0, 0.2); + box-sizing: border-box; + transition: background-color 0.2s ease; +} +.navbar .theme-toggle .theme-toggle-icon { + position: relative; + z-index: 2; + visibility: visible; + display: inline-flex; + align-items: center; + justify-content: center; + width: 0.75rem; + height: 0.75rem; + font-size: 0.75rem; + line-height: 1; +} +.navbar .theme-toggle:hover, .navbar .theme-toggle:focus { + color: var(--attack-on-color-primary); +} +.navbar .theme-toggle:hover .theme-toggle-track, .navbar .theme-toggle:focus .theme-toggle-track { + background: rgba(0, 0, 0, 0.35); + box-shadow: 0 0 0 0.125rem rgba(255, 255, 255, 0.3); +} +.navbar .theme-toggle:focus { + outline: 0; + box-shadow: none; +} +.navbar .theme-toggle .theme-toggle-icon-light { + color: var(--attack-color-primary); +} +.navbar .theme-toggle .theme-toggle-thumb { + position: absolute; + top: 0.125rem; + left: 0.125rem; + z-index: 1; + width: 1.375rem; + height: 1.375rem; + border-radius: 50%; + background: var(--attack-on-color-primary); + box-shadow: 0 0.0625rem 0.1875rem rgba(0, 0, 0, 0.35); + transition: transform 0.2s ease; +} +.navbar .theme-toggle[data-theme-effective=dark] .theme-toggle-track { + background: var(--attack-color-secondary); +} +.navbar .theme-toggle[data-theme-effective=dark] .theme-toggle-icon-light { + color: var(--attack-on-color-primary); +} +.navbar .theme-toggle[data-theme-effective=dark] .theme-toggle-icon-dark { + color: var(--attack-color-secondary); +} +.navbar .theme-toggle[data-theme-effective=dark] .theme-toggle-thumb { + transform: translateX(1.375rem); +} +:root[data-theme=dark] .navbar .theme-toggle .theme-toggle-track { + background: var(--attack-color-secondary); +} +:root[data-theme=dark] .navbar .theme-toggle .theme-toggle-icon-light { + color: var(--attack-on-color-primary); +} +:root[data-theme=dark] .navbar .theme-toggle .theme-toggle-icon-dark { + color: var(--attack-color-secondary); +} +:root[data-theme=dark] .navbar .theme-toggle .theme-toggle-thumb { + transform: translateX(1.375rem); +} +@media (prefers-color-scheme: dark) { + :root:not([data-theme]) .navbar .theme-toggle .theme-toggle-track { + background: var(--attack-color-secondary); + } + :root:not([data-theme]) .navbar .theme-toggle .theme-toggle-icon-light { + color: var(--attack-on-color-primary); + } + :root:not([data-theme]) .navbar .theme-toggle .theme-toggle-icon-dark { + color: var(--attack-color-secondary); + } + :root:not([data-theme]) .navbar .theme-toggle .theme-toggle-thumb { + transform: translateX(1.375rem); + } } /* **** */ .nav, .faq { /* NAVIGATION Dropdown */ - /* **** */ - /* Side NAVIGATION */ - border-color: rgb(223.125, 223.125, 223.125) !important; - /* **** */ } .nav .dropdown:hover > .dropdown-menu, .faq .dropdown:hover > .dropdown-menu { @@ -1577,23 +1726,29 @@ div#sidebars { } .nav .dropdown-menu, .faq .dropdown-menu { - background-color: #303435; + background-color: var(--attack-color-primary); } .nav .dropdown-menu .dropdown-item, .faq .dropdown-menu .dropdown-item { - color: white; + color: var(--attack-on-color-primary); } .nav .dropdown-menu .dropdown-item:hover, .nav .dropdown-menu .dropdown-item:focus, .faq .dropdown-menu .dropdown-item:hover, .faq .dropdown-menu .dropdown-item:focus { - color: white; + color: var(--attack-on-color-primary); text-decoration: underline; background-color: transparent; } +.nav, +.faq { + /* **** */ + /* Side NAVIGATION */ + border-color: var(--attack-border-color-body) !important; +} .nav .heading, .faq .heading { font-size: 1.6rem; - color: rgb(106.5, 114, 120.75); + color: var(--attack-on-color-body-deemphasis); letter-spacing: 0.1875rem; pointer-events: none; } @@ -1640,33 +1795,37 @@ div#sidebars { .nav .heading-dropdown, .faq .heading-dropdown { font-size: 1.2rem; - color: #303435; + color: var(--attack-color-secondary); letter-spacing: 0.1875rem; } @media screen and (width <= 90.62rem) { .nav .heading, .faq .heading { font-size: 1.2rem; - color: rgb(106.5, 114, 120.75); + color: var(--attack-on-color-body-deemphasis); letter-spacing: 0.1875rem; } .nav .heading-dropdown, .faq .heading-dropdown { font-size: 1rem; - color: #39434c; + color: var(--attack-on-color-body); letter-spacing: 0.0625rem; } } +.nav, +.faq { + /* **** */ +} .nav .nav-link, .faq .nav-link { font-size: 1rem; padding: 0.3rem 1rem; - color: rgb(106.5, 114, 120.75); + color: var(--attack-on-color-body-deemphasis); } .nav .nav-link.expand-title, .faq .nav-link.expand-title { font-size: 1.1rem; - color: #39434c; + color: var(--attack-on-color-body); } .nav .nav-link.side, .faq .nav-link.side { @@ -1675,14 +1834,14 @@ div#sidebars { } .nav .nav-link.side:hover, .faq .nav-link.side:hover { - background-color: #303435; - color: white; + background-color: var(--attack-color-primary); + color: var(--attack-on-color-primary); } .nav .nav-link.side.active, .faq .nav-link.side.active { - color: #303435; - background-color: rgb(242.25, 242.25, 242.25); - border-right: 0.1875rem solid #303435; + color: var(--attack-color-primary); + background-color: var(--attack-color-body-alternate); + border-right: 0.1875rem solid var(--attack-color-primary); } /* **** */ @@ -1693,7 +1852,7 @@ div#sidebars { cursor: col-resize; height: 100%; position: absolute; - background-color: #dfdfdf; + background-color: var(--attack-border-color-body); } .data-sources-menu { @@ -1725,11 +1884,11 @@ div#sidebars { } } .sidebar.nav .sidenav-wrapper .heading { - border-bottom: 1px solid rgb(242.25, 242.25, 242.25); + border-bottom: 1px solid var(--attack-color-body-alternate); flex: 0 1 0; } .sidebar.nav .sidenav-wrapper .checkbox-div { - border-bottom: 1px solid rgb(242.25, 242.25, 242.25); + border-bottom: 1px solid var(--attack-color-body-alternate); flex: 0 1 0; } .sidebar.nav .sidenav-wrapper .sidenav-list { @@ -1748,11 +1907,11 @@ div#sidebars { .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head a, .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head .expand-button { cursor: pointer; - color: black; + color: var(--attack-on-color-body); } .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head a:hover, .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head .expand-button:hover { - background: rgb(242.25, 242.25, 242.25); + background: var(--attack-color-body-alternate); } .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head a, .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head span { @@ -1768,7 +1927,7 @@ div#sidebars { } .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head .expand-button { padding: 5px; - border-left: 1px solid rgb(242.25, 242.25, 242.25); + border-left: 1px solid var(--attack-color-body-alternate); display: inline-block; display: flex; flex-direction: row; @@ -1787,7 +1946,7 @@ div#sidebars { display: inline-block; vertical-align: top; background-position: center; - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%2339434C' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); + background-image: var(--attack-select-arrow); z-index: 1; transition: all 0.2s ease; } @@ -1795,9 +1954,9 @@ div#sidebars { transform: rotate(-180deg); } .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head.active, .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-head.active > a { - color: #303435 !important; + color: var(--attack-color-active) !important; font-weight: bolder; - background: #eaeaea; + background: var(--attack-color-body-alternate-strong); font-family: Roboto-Bold, sans-serif; } .sidebar.nav .sidenav-wrapper .sidenav-list .sidenav .sidenav-body { @@ -1834,8 +1993,8 @@ div#sidebars { } } .search-word-found { - background: yellow; - color: black; + background: var(--attack-color-search-highlight); + color: var(--attack-on-color-search-highlight); } .btn-group-text { @@ -1858,8 +2017,8 @@ div#sidebars { } .overlay.search .overlay-inner { border-radius: 25px; - background: white; - color: #39434c; + background: var(--attack-color-body); + color: var(--attack-on-color-body); width: 100%; height: 100%; display: flex; @@ -1880,8 +2039,8 @@ div#sidebars { line-height: 50px; width: 100%; border: 0; - background: white; - color: #39434c; + background: var(--attack-color-body); + color: var(--attack-on-color-body); } .overlay.search .overlay-inner .search-header .search-input input:focus { outline: none; @@ -1919,10 +2078,10 @@ div#sidebars { .overlay.search .overlay-inner .search-filters button, .overlay.search .overlay-inner .search-filters .search-filter-chip { min-height: 36px; - border: 1px solid rgb(223.125, 223.125, 223.125); + border: 1px solid var(--attack-border-color-body); border-radius: 4px; - background: white; - color: #39434c; + background: var(--attack-color-body); + color: var(--attack-on-color-body); cursor: pointer; } .overlay.search .overlay-inner .search-filters .search-filter-summary { @@ -1935,7 +2094,7 @@ div#sidebars { font-weight: 600; } .overlay.search .overlay-inner .search-filters .search-filter-summary-chip.open { - border-color: #303435; + border-color: var(--attack-color-active); } .overlay.search .overlay-inner .search-filters .search-filter-dropdown { position: relative; @@ -1949,9 +2108,9 @@ div#sidebars { min-width: 240px; max-width: min(420px, 100vw - 100px); padding: 12px; - border: 1px solid rgb(223.125, 223.125, 223.125); + border: 1px solid var(--attack-border-color-body); border-radius: 8px; - background: white; + background: var(--attack-color-body); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); } .overlay.search .overlay-inner .search-filters .search-filter-group-heading { @@ -1994,9 +2153,9 @@ div#sidebars { border: 0; } .overlay.search .overlay-inner .search-filters .search-filter-chip.selected { - border-color: #303435; - background: #303435; - color: #eaeaea; + border-color: var(--attack-color-active); + background: var(--attack-color-active); + color: var(--attack-on-color-active); } .overlay.search .overlay-inner .search-filters .search-filter-count { margin-left: 4px; @@ -2012,7 +2171,7 @@ div#sidebars { flex-direction: column; min-height: 0; padding: 0 50px; - border-top: 1px solid rgb(223.125, 223.125, 223.125); + border-top: 1px solid var(--attack-border-color-body); margin-bottom: 25px; overflow: hidden; scrollbar-gutter: stable; @@ -2037,17 +2196,18 @@ div#sidebars { padding: 2px 8px; border: 1px solid; border-radius: 4px; - color: white; + color: var(--attack-on-color-active); font-size: 0.8rem; font-weight: 700; } .overlay.search .overlay-inner .search-body .results .search-result-badge-page-type { - border-color: #303435; - background: rgb(59.925, 63.325, 64.175); + border-color: var(--attack-color-active); + background: var(--attack-color-active-alternate-medium); } .overlay.search .overlay-inner .search-body .results .search-result-badge-domain { - border-color: #303435; - background: #303435; + border-color: var(--attack-color-deemphasis); + background: var(--attack-color-deemphasis); + color: var(--attack-on-color-deemphasis); } .overlay.search .overlay-inner .search-body .results .search-no-results .preview { display: flex; @@ -2072,8 +2232,8 @@ div#sidebars { justify-content: flex-end; min-height: 58px; padding: 12px 0 14px; - border-top: 1px solid rgb(223.125, 223.125, 223.125); - background: white; + border-top: 1px solid var(--attack-border-color-body); + background: var(--attack-color-body); } .overlay.search .overlay-inner .search-body .search-results-pagination:empty { display: none; @@ -2088,17 +2248,17 @@ div#sidebars { .overlay.search .overlay-inner .search-body .search-pagination button { min-height: 36px; padding: 5px 12px; - border: 1px solid rgb(223.125, 223.125, 223.125); + border: 1px solid var(--attack-border-color-body); border-radius: 4px; - background: white; - color: #39434c; + background: var(--attack-color-body); + color: var(--attack-on-color-body); cursor: pointer; font-weight: 700; } .overlay.search .overlay-inner .search-body .search-pagination button.current { - border-color: #303435; - background: #303435; - color: #eaeaea; + border-color: var(--attack-color-active); + background: var(--attack-color-active); + color: var(--attack-on-color-active); } .overlay.search .overlay-inner .search-body .search-pagination button:disabled { cursor: default; @@ -2121,7 +2281,7 @@ div#sidebars { align-items: center; min-height: 36px; padding: 0 4px; - color: #303435; + color: var(--attack-color-deemphasis); font-weight: 700; } .overlay.search .overlay-inner .search-body .search-pagination-icon { @@ -2208,27 +2368,27 @@ div#sidebars { margin-bottom: 5px !important; } } -.popover { - box-shadow: 0 0 5px 3px white; - border-color: #303435; -} .popover .popover-header { - background: #303435; - color: white; + background: var(--attack-color-primary); + color: var(--attack-on-color-primary); +} +.popover { + box-shadow: 0 0 5px 3px var(--attack-color-body); + border-color: var(--attack-color-primary); } .popover.bs-popover-left .arrow::before { - border-left-color: #303435; + border-left-color: var(--attack-color-primary); } .popover.bs-popover-right .arrow::before { - border-right-color: #303435; + border-right-color: var(--attack-color-primary); } .popover.bs-popover-top .arrow::before { - border-top-color: #303435; + border-top-color: var(--attack-color-primary); } .popover.bs-popover-bottom .arrow::before, .popover.bs-popover-bottom .arrow::after, .popover.bs-popover-bottom .popover-header::before { - border-bottom-color: #303435; + border-bottom-color: var(--attack-color-primary); } .tour-backdrop, @@ -2237,21 +2397,21 @@ div#sidebars { } .matrix-container { - border: 1px solid rgb(223.125, 223.125, 223.125); - background: white; + border: 1px solid var(--attack-border-color-body); + background: var(--attack-color-body); } .matrix-container + .matrix-container { margin-top: 1rem; } .matrix-container .matrix-border { - border-left: 1px solid rgb(223.125, 223.125, 223.125); + border-left: 1px solid var(--attack-border-color-body); padding-left: 0.5rem; display: flex; justify-content: center; align-items: center; } .matrix-container .matrix-title { - border-bottom: 1px solid rgb(223.125, 223.125, 223.125); + border-bottom: 1px solid var(--attack-border-color-body); margin-bottom: 1rem; padding-bottom: 0.5rem; } @@ -2278,35 +2438,35 @@ div#sidebars { right: 0; } .matrix-container .scroll-indicator-group .scroll-indicator.right.show .cover { - background: linear-gradient(to right, rgba(255, 255, 255, 0.001), white); + background: linear-gradient(to right, rgba(255, 255, 255, 0.001), var(--attack-color-body)); } .matrix-container .scroll-indicator-group .scroll-indicator.left .cover { left: 0; } .matrix-container .scroll-indicator-group .scroll-indicator.left.show .cover { - background: linear-gradient(to left, rgba(255, 255, 255, 0.001), white); + background: linear-gradient(to left, rgba(255, 255, 255, 0.001), var(--attack-color-body)); } .matrix { white-space: normal; line-height: 14px; } -.matrix.side .tactic { - padding: 2px 5px; - width: 1%; - vertical-align: top; -} .matrix.side .tactic:first-child { padding: 2px 5px 2px 2px; } .matrix.side .tactic:last-child { padding: 2px 2px 2px 5px; } +.matrix.side .tactic { + padding: 2px 5px; + width: 1%; + vertical-align: top; +} .matrix.side .tactic:hover:not(.name, .count) { - background: rgb(223.125, 223.125, 223.125); + background: var(--attack-background-color-body); } .matrix.side .tactic:hover:not(.name, .count) .sidebar.expanded .angle { - background: rgb(223.125, 223.125, 223.125); + background: var(--attack-background-color-body); } .matrix.side .tactic.name, .matrix.side .tactic.count { text-align: center; @@ -2317,7 +2477,7 @@ div#sidebars { } .matrix.side .tactic.count { font-size: 13px; - border-bottom: 1px solid black; + border-bottom: 1px solid var(--attack-border-color-body); padding-bottom: 5px; margin-bottom: 5px; } @@ -2343,23 +2503,23 @@ div#sidebars { vertical-align: top; } .matrix.side .tactic .supertechnique td.technique { - outline: 1px solid rgb(106.5, 114, 120.75); + outline: 1px solid var(--attack-on-color-body-deemphasis); outline-offset: -1px; } +.matrix.side .tactic .subtechniques.hidden { + display: none; +} .matrix.side .tactic .subtechniques { display: flex; flex-direction: column; height: 100%; margin-left: -1px; - border-left: 2px solid rgb(106.5, 114, 120.75); - outline: 1px solid rgb(106.5, 114, 120.75); + border-left: 2px solid var(--attack-on-color-body-deemphasis); + outline: 1px solid var(--attack-on-color-body-deemphasis); outline-offset: -1px; white-space: nowrap; vertical-align: top; } -.matrix.side .tactic .subtechniques.hidden { - display: none; -} .matrix.side .tactic .subtechniques .subtechnique { height: 100%; flex-grow: 1; @@ -2368,7 +2528,7 @@ div#sidebars { text-align: center; vertical-align: middle; transform: rotate(-90deg); - color: rgb(242.25, 242.25, 242.25); + color: var(--attack-color-body-alternate); width: 12px; height: 12px; font-size: 16px; @@ -2378,7 +2538,7 @@ div#sidebars { min-width: 8px; width: 12px; padding: 0; - background: rgb(106.5, 114, 120.75); + background: var(--attack-on-color-body-deemphasis); cursor: pointer; position: relative; vertical-align: middle; @@ -2391,10 +2551,10 @@ div#sidebars { height: 12px; display: block; position: absolute; - background: white; + background: var(--attack-color-body); } .matrix.side .tactic .sidebar.expanded .angle svg { - fill: rgb(106.5, 114, 120.75); + fill: var(--attack-on-color-body-deemphasis); vertical-align: baseline; } .matrix.side .tactic .sidebar.expanded .angle.top { @@ -2417,7 +2577,7 @@ div#sidebars { } .matrix.flat .tactic.count { font-size: 13px; - border-bottom: 1px solid black; + border-bottom: 1px solid var(--attack-border-color-body); padding-bottom: 5px; margin-bottom: 5px; } @@ -2438,7 +2598,7 @@ div#sidebars { min-width: 8px; width: 12px; padding: 0; - background: rgb(106.5, 114, 120.75); + background: var(--attack-on-color-body-deemphasis); cursor: pointer; vertical-align: middle; } @@ -2446,21 +2606,21 @@ div#sidebars { text-align: center; vertical-align: middle; transform: rotate(-90deg); - color: rgb(242.25, 242.25, 242.25); + color: var(--attack-color-body-alternate); width: 12px; height: 9px; font-size: 16px; line-height: 12px; } .matrix.flat .tactic .supertechnique td.sidebar.subtechniques svg { - fill: rgb(106.5, 114, 120.75); + fill: var(--attack-on-color-body-deemphasis); vertical-align: baseline; } .matrix.flat .tactic .supertechnique td.sidebar { - border-right: 2px solid rgb(106.5, 114, 120.75); + border-right: 2px solid var(--attack-on-color-body-deemphasis); } .matrix.flat .tactic .supertechnique td.technique { - outline: 1px solid rgb(106.5, 114, 120.75); + outline: 1px solid var(--attack-on-color-body-deemphasis); outline-offset: -1px; } .matrix.flat .tactic .more-icon { @@ -2477,11 +2637,9 @@ div#sidebars { height: 100%; display: flex; align-items: center; - background-color: white; + background-color: var(--attack-color-body); font-size: 13px; line-height: 14px; - outline: 1px solid transparent; - outline-offset: -1px; } .matrix .technique-cell a { display: block; @@ -2489,8 +2647,12 @@ div#sidebars { height: 100%; padding: 7px 3px; } +.matrix .technique-cell { + outline: 1px solid transparent; + outline-offset: -1px; +} .matrix .technique-cell:not(.colored):not(.supertechniquecell) { - outline-color: rgb(223.125, 223.125, 223.125); + outline-color: var(--attack-border-color-body); } .matrix-controls { @@ -2499,17 +2661,17 @@ div#sidebars { padding: 1rem; } .matrix-controls button { - border-color: rgb(223.125, 223.125, 223.125); - background: white; - color: #39434c; + border-color: var(--attack-border-color-body); + background: var(--attack-color-body); + color: var(--attack-on-color-body); } .matrix-controls button:hover { - background: rgb(244.8, 244.8, 244.8); + background: var(--attack-color-body-alternate-subtle); } .matrix-controls .layout-button:active { - color: #16181b; + color: var(--attack-on-color-body); text-decoration: none; - background-color: #f8f9fa; + background-color: var(--attack-color-body-alternate-subtle); } .center-controls .matrix-controls .btn-toolbar { @@ -2528,9 +2690,275 @@ div#sidebars { } .version-table .table-break-row { - border-right-color: white; - border-left-color: white; + border-right-color: var(--attack-color-body); + border-left-color: var(--attack-color-body); padding: 1rem 0; } +:root, +:root[data-theme=light] { + --attack-color-primary: #303435; + --attack-on-color-primary: white; + --attack-color-secondary: #303435; + --attack-color-secondary-hover: rgb(57.7188118812, 62.5287128713, 63.7311881188); + --attack-on-color-secondary: white; + --attack-color-footer: #303435; + --attack-on-color-footer: #87deff; + --attack-color-footer-link-hover: rgb(99, 167, 206.5); + --attack-color-active: #303435; + --attack-color-active-alternate-medium: rgb(59.925, 63.325, 64.175); + --attack-on-color-active: #eaeaea; + --attack-color-body: white; + --attack-on-color-body: #39434c; + --attack-on-color-body-emphasis: #1d2226; + --attack-color-property-label: #1d2226; + --attack-on-color-body-deemphasis: #6b7379; + --attack-color-body-alternate-subtle: #f5f5f5; + --attack-color-body-alternate: #f2f2f2; + --attack-color-body-alternate-strong: #e6e6e6; + --attack-color-body-alternate-strongest: #d9d9d9; + --attack-border-color-body: #dfdfdf; + --attack-background-color-body: #dfdfdf; + --attack-color-link: #3f709e; + --attack-color-link-hover: #0056b3; + --attack-color-matrix-header: gray; + --attack-on-color-matrix-header: white; + --attack-color-search-highlight: yellow; + --attack-on-color-search-highlight: black; + --attack-color-deemphasis: #686f75; + --attack-on-color-deemphasis: white; + --attack-color-card-header: rgb(57 67 76 / 3%); + --attack-color-code: #a52f16; + --attack-color-danger: #bd2130; + --attack-color-image-background: white; + --attack-color-banner: #e7f0f6; + --attack-on-color-banner: #263b4a; + --attack-border-color-banner: #c2d5e2; + --attack-select-arrow: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%2339434c' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'/%3E%3C/svg%3E"); + color-scheme: light; +} + +@media (prefers-color-scheme: dark) { + :root:not([data-theme]) { + --attack-color-primary: #303435; + --attack-on-color-primary: white; + --attack-color-secondary: #303435; + --attack-color-secondary-hover: rgb(63.5500990099, 68.8459405941, 70.1699009901); + --attack-on-color-secondary: white; + --attack-color-footer: #303435; + --attack-on-color-footer: #87deff; + --attack-color-footer-link-hover: #b7edff; + --attack-color-active: #9aa3a6; + --attack-color-active-alternate-medium: #879195; + --attack-on-color-active: #0f171c; + --attack-color-body: #222426; + --attack-on-color-body: #e8e6e3; + --attack-on-color-body-emphasis: #fffaf4; + --attack-color-property-label: #f2d2a4; + --attack-on-color-body-deemphasis: #b7b1a8; + --attack-color-body-alternate-subtle: #272a2c; + --attack-color-body-alternate: #2b2e30; + --attack-color-body-alternate-strong: #303437; + --attack-color-body-alternate-strongest: #353a3d; + --attack-border-color-body: #596166; + --attack-background-color-body: #373d40; + --attack-color-link: #7bb8ee; + --attack-color-link-hover: #b7ddff; + --attack-color-matrix-header: #596166; + --attack-on-color-matrix-header: #fffaf4; + --attack-color-search-highlight: #665a00; + --attack-on-color-search-highlight: #fff4b8; + --attack-color-deemphasis: #b7b1a8; + --attack-on-color-deemphasis: #222426; + --attack-color-card-header: #2b2e30; + --attack-color-code: #ff8f70; + --attack-color-danger: #ff8c96; + --attack-color-image-background: white; + --attack-color-banner: #263a49; + --attack-on-color-banner: #f2f7fa; + --attack-border-color-banner: #3f5d72; + --attack-select-arrow: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%23e8e6e3' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'/%3E%3C/svg%3E"); + color-scheme: dark; + } +} +:root[data-theme=dark] { + --attack-color-primary: #303435; + --attack-on-color-primary: white; + --attack-color-secondary: #303435; + --attack-color-secondary-hover: rgb(63.5500990099, 68.8459405941, 70.1699009901); + --attack-on-color-secondary: white; + --attack-color-footer: #303435; + --attack-on-color-footer: #87deff; + --attack-color-footer-link-hover: #b7edff; + --attack-color-active: #9aa3a6; + --attack-color-active-alternate-medium: #879195; + --attack-on-color-active: #0f171c; + --attack-color-body: #222426; + --attack-on-color-body: #e8e6e3; + --attack-on-color-body-emphasis: #fffaf4; + --attack-color-property-label: #f2d2a4; + --attack-on-color-body-deemphasis: #b7b1a8; + --attack-color-body-alternate-subtle: #272a2c; + --attack-color-body-alternate: #2b2e30; + --attack-color-body-alternate-strong: #303437; + --attack-color-body-alternate-strongest: #353a3d; + --attack-border-color-body: #596166; + --attack-background-color-body: #373d40; + --attack-color-link: #7bb8ee; + --attack-color-link-hover: #b7ddff; + --attack-color-matrix-header: #596166; + --attack-on-color-matrix-header: #fffaf4; + --attack-color-search-highlight: #665a00; + --attack-on-color-search-highlight: #fff4b8; + --attack-color-deemphasis: #b7b1a8; + --attack-on-color-deemphasis: #222426; + --attack-color-card-header: #2b2e30; + --attack-color-code: #ff8f70; + --attack-color-danger: #ff8c96; + --attack-color-image-background: white; + --attack-color-banner: #263a49; + --attack-on-color-banner: #f2f7fa; + --attack-border-color-banner: #3f5d72; + --attack-select-arrow: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%23e8e6e3' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'/%3E%3C/svg%3E"); + color-scheme: dark; +} + +.form-control, +.custom-select, +.bootstrap-select > .dropdown-toggle, +.dropdown-menu, +.list-group-item, +.modal-content, +.popover, +.popover-body, +.page-link, +.input-group-text { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body); + border-color: var(--attack-border-color-body); +} + +.custom-select { + background-image: var(--attack-select-arrow); +} + +.form-control:focus, +.custom-select:focus { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body); + border-color: var(--attack-color-active); + box-shadow: 0 0 0 0.2rem rgba(76, 159, 254, 0.25); +} + +.dropdown-item, +.page-link { + color: var(--attack-color-link); +} + +.dropdown-item:hover, +.dropdown-item:focus, +.page-link:hover, +.page-link:focus { + color: var(--attack-on-color-body-emphasis); + background-color: var(--attack-color-body-alternate); +} + +.form-control:disabled, +.form-control[readonly], +.custom-select:disabled, +.page-item.disabled .page-link { + color: var(--attack-on-color-body-deemphasis); + background-color: var(--attack-color-body-alternate); +} + +.dropdown-divider, +hr { + border-color: var(--attack-border-color-body); +} + +.table { + color: var(--attack-on-color-body); +} + +.text-danger { + color: var(--attack-color-danger) !important; +} + +.btn-outline-secondary { + color: var(--attack-on-color-body); + border-color: var(--attack-on-color-body-deemphasis); +} +.btn-outline-secondary:disabled, .btn-outline-secondary.disabled { + color: var(--attack-on-color-body-deemphasis); + background-color: transparent; +} +.btn-outline-secondary:not(:disabled, .disabled):hover, .btn-outline-secondary:not(:disabled, .disabled):focus, .btn-outline-secondary:not(:disabled, .disabled):active { + color: var(--attack-on-color-active); + background-color: var(--attack-color-active); + border-color: var(--attack-color-active); +} + +.nav-tabs .nav-link { + color: var(--attack-color-link); + border-color: transparent; +} + +.nav-tabs .nav-link:hover, +.nav-tabs .nav-link:focus { + border-color: var(--attack-border-color-body); +} + +.nav-tabs .nav-link.active, +.nav-tabs .nav-item.show .nav-link { + color: var(--attack-on-color-body); + background-color: var(--attack-color-body); + border-color: var(--attack-border-color-body) var(--attack-border-color-body) var(--attack-color-body); +} + +@media print { + :root, + :root[data-theme], + :root:not([data-theme]) { + --attack-color-primary: #303435; + --attack-on-color-primary: white; + --attack-color-secondary: #303435; + --attack-color-secondary-hover: rgb(57.7188118812, 62.5287128713, 63.7311881188); + --attack-on-color-secondary: white; + --attack-color-footer: #303435; + --attack-on-color-footer: #87deff; + --attack-color-footer-link-hover: rgb(99, 167, 206.5); + --attack-color-active: #303435; + --attack-color-active-alternate-medium: rgb(59.925, 63.325, 64.175); + --attack-on-color-active: #eaeaea; + --attack-color-body: white; + --attack-on-color-body: #39434c; + --attack-on-color-body-emphasis: #1d2226; + --attack-color-property-label: #1d2226; + --attack-on-color-body-deemphasis: #6b7379; + --attack-color-body-alternate-subtle: #f5f5f5; + --attack-color-body-alternate: #f2f2f2; + --attack-color-body-alternate-strong: #e6e6e6; + --attack-color-body-alternate-strongest: #d9d9d9; + --attack-border-color-body: #dfdfdf; + --attack-background-color-body: #dfdfdf; + --attack-color-link: #3f709e; + --attack-color-link-hover: #0056b3; + --attack-color-matrix-header: gray; + --attack-on-color-matrix-header: white; + --attack-color-search-highlight: yellow; + --attack-on-color-search-highlight: black; + --attack-color-deemphasis: #686f75; + --attack-on-color-deemphasis: white; + --attack-color-card-header: rgb(57 67 76 / 3%); + --attack-color-code: #a52f16; + --attack-color-danger: #bd2130; + --attack-color-image-background: white; + --attack-color-banner: #e7f0f6; + --attack-on-color-banner: #263b4a; + --attack-border-color-banner: #c2d5e2; + --attack-select-arrow: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%2339434c' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'/%3E%3C/svg%3E"); + color-scheme: light; + } +} + /*# sourceMappingURL=style-user.css.map */ diff --git a/attack-theme/templates/general/attack-index.html b/attack-theme/templates/general/attack-index.html index 21dd4fbd02d..8aafc84de2a 100644 --- a/attack-theme/templates/general/attack-index.html +++ b/attack-theme/templates/general/attack-index.html @@ -28,7 +28,7 @@ Contribute - Blog External site + Blog External site @@ -40,7 +40,7 @@ Random Page - Toggle Dropdown @@ -115,4 +115,4 @@

{{parsed.matrix_name}}

-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/attack-theme/templates/general/base-template.html b/attack-theme/templates/general/base-template.html index c960c51d76b..ab8e06538de 100644 --- a/attack-theme/templates/general/base-template.html +++ b/attack-theme/templates/general/base-template.html @@ -30,8 +30,10 @@ + {{ title }} + diff --git a/attack-theme/templates/macros/navigation_menu.html b/attack-theme/templates/macros/navigation_menu.html index c842b4d1027..64756461955 100644 --- a/attack-theme/templates/macros/navigation_menu.html +++ b/attack-theme/templates/macros/navigation_menu.html @@ -1,10 +1,12 @@ {% macro navigation_menu(menu, logo_header, output_file) -%}