Skip to content

Commit

Permalink
Updated GitHub Page styles
Browse files Browse the repository at this point in the history
jharper-sec committed Dec 6, 2024
1 parent a946028 commit 2c6812e
Showing 7 changed files with 1,007 additions and 334 deletions.
60 changes: 57 additions & 3 deletions _config.yaml
Original file line number Diff line number Diff line change
@@ -1,43 +1,60 @@
# Site settings
title: Application Detection and Response Runbooks
description: Security incident response runbooks for Contrast Security's Application Detection and Response
description: >-
Comprehensive security incident response runbooks for Contrast Security's
Application Detection and Response platform, providing step-by-step guidance
for security teams.
baseurl: "/adr-runbooks"
url: "https://contrast-security-oss.github.io"
lang: en
author: Contrast Security
logo: /assets/images/contrast-logo.svg

# Build settings
markdown: kramdown
remote_theme: pages-themes/minimal@v0.2.0
sass:
style: compressed

plugins:
- jekyll-remote-theme
- jekyll-seo-tag
- jekyll-sitemap
- jekyll-last-modified-at

# Collections
collections:
runbooks:
output: true
permalink: /:collection/:name/

# Default front matter for runbook files
# Default front matter
defaults:
- scope:
path: ""
type: "runbooks"
values:
layout: "runbook"
schema: SecurityRunbook
- scope:
path: ""
values:
layout: "default"
image: /assets/images/contrast-logo.png

# Navigation
nav_links:
- title: Home
url: /
description: Return to homepage
- title: Runbooks
url: /runbooks/
description: Browse all security runbooks
- title: About
url: /about/
description: Learn about this project

# Theme customization
# Theme colors
colors:
deep_green: "#004D45"
medium_green: "#0E9E53"
@@ -48,3 +65,40 @@ colors:
bright_green: "#40D273"
light_blue: "#00CFFE"
platinum_grey: "#9DA5B3"

# Social/SEO
twitter:
username: ContrastSec
card: summary
social:
name: Contrast Security
links:
- https://twitter.com/ContrastSec
- https://github.com/Contrast-Security-OSS
- https://www.linkedin.com/company/contrast-security

# Analytics
google_analytics: # TODO: Add your GA tracking ID

# Additional settings
timezone: UTC
encoding: utf-8
exclude:
- Gemfile
- Gemfile.lock
- node_modules
- vendor
- CONTRIBUTING.md
- README.md
- LICENSE
- CNAME
- package.json
- package-lock.json

# Performance
compress_html:
clippings: all
comments: all
endings: all
blanklines: false
profile: false
124 changes: 124 additions & 0 deletions _layouts/default.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<!DOCTYPE html>
<html lang="{{ site.lang | default: 'en' }}">

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description"
content="{{ page.description | default: site.description | strip_html | normalize_whitespace | truncate: 160 | escape }}">

<!-- SEO -->
{% seo %}

<!-- Open Graph -->
<meta property="og:site_name" content="{{ site.title }}">
<meta property="og:locale" content="{{ site.lang | default: 'en_US' }}">
{% if page.image %}
<meta property="og:image" content="{{ page.image | absolute_url }}">
{% endif %}

<!-- Twitter Card -->
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@{{ site.twitter.username }}">

<!-- Preload critical assets -->
<link rel="preload" href="{{ '/assets/css/style.css' | relative_url }}" as="style">
<link rel="preload" href="{{ '/assets/js/anchor-headings.js' | relative_url }}" as="script">

<!-- Stylesheets -->
<link rel="stylesheet" href="{{ '/assets/css/style.css' | relative_url }}">
{% if page.layout == 'runbook' %}
<link rel="stylesheet" href="{{ '/assets/css/runbook.css' | relative_url }}">
{% endif %}
{% if page.layout == 'index' %}
<link rel="stylesheet" href="{{ '/assets/css/index.css' | relative_url }}">
{% endif %}

<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="{{ '/assets/images/contrast-logo.svg' | relative_url }}">
<link rel="alternate icon" type="image/png" href="{{ '/assets/images/favicon.png' | relative_url }}">

<!-- Schema.org -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "{% if page.schema %}{{ page.schema }}{% else %}WebSite{% endif %}",
"name": "{{ site.title }}",
"url": "{{ site.url }}",
"description": "{{ site.description }}",
"publisher": {
"@type": "Organization",
"name": "{{ site.author }}",
"logo": {
"@type": "ImageObject",
"url": "{{ site.logo | absolute_url }}"
}
}
}
</script>

{% if site.google_analytics %}
<!-- Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id={{ site.google_analytics }}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', '{{ site.google_analytics }}');
</script>
{% endif %}
</head>

<body>
<!-- Skip navigation -->
<a href="#main-content" class="skip-nav">Skip to main content</a>

<!-- Navigation -->
<nav class="nav-wrapper" role="navigation" aria-label="Main navigation">
<div class="container">
<div class="nav-brand">
<a href="{{ '/' | relative_url }}" aria-label="Return to homepage">
<img src="{{ site.logo | relative_url }}" alt="{{ site.title }}" class="nav-logo" width="150"
height="40">
</a>
</div>

<button class="nav-toggle" aria-label="Toggle navigation menu" aria-expanded="false">
<span class="nav-toggle-icon"></span>
</button>

<div class="nav-links">
{% for link in site.nav_links %}
<a href="{{ link.url | relative_url }}" {% if page.url==link.url %}aria-current="page" class="active" {%
endif %} title="{{ link.description }}">
{{ link.title }}
</a>
{% endfor %}
</div>
</div>
</nav>

<!-- Main content -->
<main id="main-content" role="main">
{{ content }}
</main>

<!-- Footer -->
<footer class="site-footer" role="contentinfo">
<div class="container">
<p>&copy; {{ site.time | date: '%Y' }} {{ site.author }}. All rights reserved.</p>
<p>For more information, visit <a href="https://www.contrastsecurity.com">contrastsecurity.com</a></p>
{% if site.github.repository_url %}
<p><a href="{{ site.github.repository_url }}">View on GitHub</a></p>
{% endif %}
</div>
</footer>

<!-- Scripts -->
<script src="{{ '/assets/js/anchor-headings.js' | relative_url }}" defer></script>
{% if page.layout == 'runbook' %}
<script src="{{ '/assets/js/runbook.js' | relative_url }}" defer></script>
{% endif %}
</body>

</html>
89 changes: 63 additions & 26 deletions _layouts/index.html
Original file line number Diff line number Diff line change
@@ -1,32 +1,69 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ page.title }}</title>
<link rel="stylesheet" href="{{ '/assets/css/style.css' | relative_url }}">
<link rel="stylesheet" href="{{ '/assets/css/index.css' | relative_url }}">
</head>
<body>
<div class="hero">
<div class="container">
<h1>{{ page.title }}</h1>
<p class="hero-description">{{ page.description }}</p>
<a href="https://github.com/Contrast-Security-OSS/adr-runbooks" class="github-button">
View the Project on GitHub
---
layout: default
---

<header class="hero" role="banner">
<div class="container">
<h1>{{ page.title }}</h1>
{% if page.description %}
<p class="hero-description">{{ page.description }}</p>
{% endif %}
<div class="hero-actions">
<a href="{{ site.github.repository_url }}" class="button button-primary">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.17 6.84 9.49.5.09.68-.22.68-.48v-1.71c-2.78.6-3.37-1.34-3.37-1.34-.46-1.16-1.11-1.47-1.11-1.47-.91-.62.07-.61.07-.61 1 .07 1.53 1.03 1.53 1.03.89 1.52 2.34 1.08 2.91.83.09-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.94 0-1.09.39-1.98 1.03-2.68-.1-.25-.45-1.27.1-2.64 0 0 .84-.27 2.75 1.02.8-.22 1.65-.33 2.5-.33.85 0 1.7.11 2.5.33 1.91-1.29 2.75-1.02 2.75-1.02.55 1.37.2 2.39.1 2.64.64.7 1.03 1.59 1.03 2.68 0 3.84-2.34 4.69-4.57 4.93.36.31.68.92.68 1.85v2.74c0 .27.18.58.69.48C19.14 20.17 22 16.42 22 12c0-5.523-4.477-10-10-10z"
fill="currentColor" />
</svg>
View on GitHub
</a>
</div>
</div>
</header>

<main class="container" role="main">
<div class="runbooks-grid">
{% for category in site.data.categories %}
<section class="runbook-category">
<h2>{{ category.name }}</h2>
{% if category.description %}
<p class="category-description">{{ category.description }}</p>
{% endif %}

<div class="runbook-list">
{% assign category_runbooks = site.runbooks | where: "category", category.id %}
{% for runbook in category_runbooks %}
<article class="runbook-card">
<h3 class="runbook-title">
<a href="{{ runbook.url | relative_url }}">{{ runbook.title }}</a>
</h3>
{% if runbook.description %}
<p class="runbook-description">{{ runbook.description }}</p>
{% endif %}
<div class="runbook-meta">
<span class="runbook-date">
Last updated: {{ runbook.last_modified_at | default: runbook.date | date: "%B %d, %Y" }}
</span>
</div>
</article>
{% endfor %}
</div>
</section>
{% endfor %}
</div>

<main class="container">
<section class="content-section">
{{ content }}
</main>
</section>
</main>

<footer>
<div class="container">
<p>© {{ site.time | date: '%Y' }} Contrast Security. All rights reserved.</p>
<p>For more information, visit <a href="https://www.contrastsecurity.com">contrastsecurity.com</a></p>
</div>
</footer>
</body>
</html>
<aside class="contribution-cta">
<div class="container">
<h2>Contributing</h2>
<p>Help improve these runbooks by contributing your expertise.</p>
<a href="{{ site.github.repository_url }}/blob/main/CONTRIBUTING.md" class="button button-secondary">
Learn how to contribute
</a>
</div>
</aside>
197 changes: 143 additions & 54 deletions assets/css/index.css
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
/* Index page specific styles */
:root {
--deep-green: #004D45;
--medium-green: #0E9E53;
--midnight-blue: #0A004F;
--blue: #3877FF;
--bright-green: #40D273;
}

/* Hero section */
.hero {
background-color: var(--deep-green);
color: white;
@@ -18,88 +12,151 @@
margin: 0 0 1rem 0;
font-size: 2.5rem;
line-height: 1.2;
color: white;
}

.hero-description {
font-size: 1.25rem;
margin-bottom: 1.5rem;
opacity: 0.9;
max-width: 600px;
}

.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1.5rem;
.hero-actions {
display: flex;
gap: 1rem;
margin-top: 2rem;
}

.github-button {
display: inline-block;
background-color: var(--medium-green);
color: white;
padding: 0.75rem 1.5rem;
border-radius: 4px;
text-decoration: none;
font-weight: 500;
transition: background-color 0.2s;
/* Runbooks Grid */
.runbooks-grid {
display: grid;
gap: 2rem;
margin: 2rem 0;
}

.github-button:hover {
background-color: var(--bright-green);
text-decoration: none;
}

main {
padding: 2rem 0;
.runbook-category {
margin-bottom: 3rem;
}

main h2 {
.runbook-category h2 {
color: var(--midnight-blue);
font-size: 2rem;
margin: 2rem 0 1rem;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid var(--medium-green);
}

main h3 {
color: var(--deep-green);
font-size: 1.5rem;
margin: 1.5rem 0 1rem;
.category-description {
color: var(--text-secondary);
margin-bottom: 1.5rem;
}

.runbook-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
}

/* Runbook Cards */
.runbook-card {
background: white;
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: transform var(--transition-base), box-shadow var(--transition-base);
}

main ul {
list-style: none;
padding: 0;
margin: 0 0 2rem 0;
.runbook-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}

main li {
margin: 0.75rem 0;
.runbook-title {
font-size: 1.25rem;
margin: 0 0 0.75rem 0;
}

main a {
color: var(--blue);
.runbook-title a {
color: var(--deep-green);
text-decoration: none;
}

main a:hover {
text-decoration: underline;
.runbook-title a:hover {
color: var(--medium-green);
}

main li span {
color: #666;
margin-left: 0.25rem;
.runbook-description {
color: var(--text-secondary);
font-size: 0.875rem;
margin-bottom: 1rem;
line-height: 1.5;
}

footer {
background-color: var(--midnight-blue);
color: white;
padding: 2rem 0;
.runbook-meta {
font-size: 0.75rem;
color: var(--platinum-grey);
}

/* Contribution CTA */
.contribution-cta {
background-color: var(--background-secondary);
padding: 4rem 0;
margin-top: 4rem;
text-align: center;
}

footer a {
.contribution-cta h2 {
color: var(--midnight-blue);
margin-bottom: 1rem;
}

.contribution-cta p {
color: var(--text-secondary);
margin-bottom: 2rem;
font-size: 1.125rem;
}

/* Buttons */
.button {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
border-radius: 4px;
font-weight: 500;
text-decoration: none;
transition: all var(--transition-base);
}

.button-primary {
background-color: var(--medium-green);
color: white;
text-decoration: underline;
}

.button-primary:hover {
background-color: var(--bright-green);
text-decoration: none;
}

.button-secondary {
background-color: white;
color: var(--deep-green);
border: 2px solid var(--deep-green);
}

.button-secondary:hover {
background-color: var(--deep-green);
color: white;
text-decoration: none;
}

.button .icon {
width: 1.25rem;
height: 1.25rem;
}

/* Responsive Design */
@media (max-width: 768px) {
.hero {
padding: 3rem 0;
@@ -109,7 +166,39 @@ footer a {
font-size: 2rem;
}

.container {
padding: 0 1rem;
.hero-description {
font-size: 1.125rem;
}

.runbook-list {
grid-template-columns: 1fr;
}

.contribution-cta {
padding: 3rem 0;
}
}

/* Print Styles */
@media print {
.hero {
background: none;
color: var(--text-primary);
padding: 2rem 0;
}

.hero h1 {
color: var(--deep-green);
}

.hero-actions,
.contribution-cta {
display: none;
}

.runbook-card {
break-inside: avoid;
box-shadow: none;
border: 1px solid var(--border-color);
}
}
181 changes: 137 additions & 44 deletions assets/css/runbook.css
Original file line number Diff line number Diff line change
@@ -1,77 +1,128 @@
/* Runbook-specific styles */
.runbook-content {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
background: white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.runbook-header {
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 2px solid var(--deep-green);
.runbook-layout {
display: grid;
grid-template-columns: var(--sidebar-width) 1fr;
gap: 2rem;
margin-top: 2rem;
}

.runbook-description {
font-size: 1.2rem;
color: var(--platinum-grey);
margin-top: 1rem;
/* Sidebar */
.sidebar {
position: sticky;
top: calc(2rem + var(--nav-height));
height: calc(100vh - var(--nav-height) - 2rem);
overflow-y: auto;
padding-right: 1rem;
}

/* Table of Contents */
.runbook-toc {
position: sticky;
top: 2rem;
background: white;
padding: 1.5rem;
background: var(--background-secondary);
border-radius: 4px;
border: 1px solid var(--platinum-grey);
padding: 1.5rem;
margin-bottom: 2rem;
}

.runbook-toc h3 {
margin-top: 0;
font-size: 1.25rem;
color: var(--midnight-blue);
margin-bottom: 1rem;
}

.toc-list {
list-style: none;
padding-left: 0;
}

.toc-list ul {
padding-left: 1.5rem;
margin: 0.5rem 0;
}

.toc-list li {
margin: 0.5rem 0;
}

/* Outcome sections */
.outcome-section {
margin: 2rem 0;
.toc-list a {
color: var(--text-primary);
text-decoration: none;
transition: color var(--transition-base);
display: inline-block;
padding: 0.25rem 0;
}

.toc-list a:hover {
color: var(--blue);
}

/* Quick Links */
.quick-links {
background: white;
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 1.5rem;
}

.quick-links h3 {
margin-top: 0;
font-size: 1.25rem;
color: var(--midnight-blue);
margin-bottom: 1rem;
}

.quick-links ul {
list-style: none;
padding-left: 0;
}

.quick-links li {
margin: 0.5rem 0;
}

/* Main Content */
.runbook-content {
background: white;
border-radius: 4px;
border-left: 4px solid var(--medium-green);
background: #f8f9fa;
padding: 2rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
min-height: calc(100vh - var(--nav-height) - 4rem);
}

.runbook-header {
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 2px solid var(--deep-green);
}

.outcome-section h2 {
color: var(--deep-green);
.runbook-header h1 {
margin-top: 0;
}

/* Example events */
.runbook-description {
font-size: 1.2rem;
color: var(--text-secondary);
margin-top: 1rem;
}

/* Event Examples */
.event-example {
background: #f6f8fa;
background: var(--background-secondary);
padding: 1rem;
border-radius: 4px;
font-family: 'Consolas', monospace;
overflow-x: auto;
margin: 1rem 0;
white-space: pre-wrap;
word-break: break-all;
}

/* Decision trees */
/* Decision Trees */
.decision-tree {
margin: 2rem 0;
padding: 1rem;
border: 1px solid var(--platinum-grey);
border: 1px solid var(--border-color);
border-radius: 4px;
}

@@ -88,9 +139,34 @@
color: var(--medium-green);
}

/* Post-incident activities */
/* Status Tags */
.status-tag {
display: inline-block;
padding: 0.25rem 0.75rem;
border-radius: 1rem;
font-size: 0.875rem;
font-weight: 500;
margin: 0 0.5rem;
}

.status-exploited {
background-color: rgba(255, 104, 98, 0.1);
color: var(--red);
}

.status-blocked {
background-color: rgba(14, 158, 83, 0.1);
color: var(--medium-green);
}

.status-ineffective {
background-color: rgba(157, 165, 179, 0.1);
color: var(--platinum-grey);
}

/* Post-incident Activities */
.post-incident {
background: #f8f9fa;
background: var(--background-secondary);
padding: 1.5rem;
border-radius: 4px;
margin: 2rem 0;
@@ -102,38 +178,55 @@
padding-bottom: 0.5rem;
}

/* Mobile responsiveness */
@media (max-width: 768px) {
.container {
/* Meta Information */
.runbook-meta {
margin-top: 4rem;
padding-top: 1rem;
border-top: 1px solid var(--border-color);
color: var(--text-secondary);
font-size: 0.875rem;
}

/* Responsive Design */
@media (max-width: 1024px) {
.runbook-layout {
grid-template-columns: 1fr;
}

.sidebar {
position: static;
width: auto;
height: auto;
margin-bottom: 2rem;
}
}

@media (max-width: 768px) {
.runbook-content {
padding: 1rem;
}

.event-example {
font-size: 0.875rem;
}
}

/* Print styles */
/* Print Styles */
@media print {

.nav-wrapper,
.sidebar,
.site-footer {
.sidebar {
display: none;
}

.runbook-layout {
display: block;
}

.runbook-content {
box-shadow: none;
padding: 0;
}

.runbook-header {
border-bottom: none;
.post-incident {
background: none;
padding: 0;
}
}
424 changes: 298 additions & 126 deletions assets/css/style.css

Large diffs are not rendered by default.

266 changes: 185 additions & 81 deletions assets/js/anchor-headings.js
Original file line number Diff line number Diff line change
@@ -1,94 +1,198 @@
// Add anchor links to headings
document.addEventListener('DOMContentLoaded', function () {
const articleContent = document.querySelector('.runbook-content');
if (!articleContent) return;

const headings = articleContent.querySelectorAll('h2, h3, h4, h5, h6');

headings.forEach(heading => {
// Skip headings that shouldn't have anchors
if (heading.classList.contains('no-anchor')) return;

// Create anchor link
const anchor = document.createElement('a');
anchor.className = 'heading-anchor';
anchor.href = `#${heading.id}`;
anchor.innerHTML = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">' +
'<path d="M7.5 4H4.5C3.67157 4 3 4.67157 3 5.5V10.5C3 11.3284 3.67157 12 4.5 12H7.5M8.5 12H11.5C12.3284 12 13 11.3284 13 10.5V5.5C13 4.67157 12.3284 4 11.5 4H8.5" ' +
'stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>' +
'<path d="M6 8H10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>' +
'</svg>';

// Add anchor link after heading text
heading.appendChild(anchor);

// Add hover behavior
heading.addEventListener('mouseenter', () => {
anchor.style.opacity = '1';
// Runbook functionality
class RunbookManager {
constructor() {
this.initializeVariables();
this.setupEventListeners();
this.handleInitialState();
}

initializeVariables() {
// Navigation elements
this.navToggle = document.querySelector('.nav-toggle');
this.navLinks = document.querySelector('.nav-links');

// Table of contents
this.toc = document.querySelector('.runbook-toc');
this.tocLinks = document.querySelectorAll('.toc-list a');

// Content sections
this.sections = document.querySelectorAll('h2[id], h3[id]');

// Current section tracking
this.currentSection = null;
this.tocObserver = null;
}

setupEventListeners() {
// Mobile navigation
if (this.navToggle) {
this.navToggle.addEventListener('click', () => this.toggleNavigation());
}

// Table of contents navigation
this.tocLinks.forEach(link => {
link.addEventListener('click', (e) => this.handleTocClick(e));
});

heading.addEventListener('mouseleave', () => {
anchor.style.opacity = '0';
// Scroll spy
this.setupScrollSpy();

// Handle initial hash
if (window.location.hash) {
this.scrollToSection(window.location.hash);
}
}

handleInitialState() {
// Set initial active section
this.updateActiveSection();

// Handle mobile nav initial state
this.handleMobileNavState();
}

toggleNavigation() {
const isExpanded = this.navToggle.getAttribute('aria-expanded') === 'true';
this.navToggle.setAttribute('aria-expanded', !isExpanded);
this.navLinks.classList.toggle('active');
}

handleTocClick(e) {
e.preventDefault();
const targetId = e.target.getAttribute('href');
this.scrollToSection(targetId);
history.pushState(null, null, targetId);
}

scrollToSection(targetId) {
const targetElement = document.querySelector(targetId);
if (!targetElement) return;

const headerOffset = 80; // Adjust based on fixed header height
const elementPosition = targetElement.getBoundingClientRect().top;
const offsetPosition = elementPosition + window.pageYOffset - headerOffset;

window.scrollTo({
top: offsetPosition,
behavior: 'smooth'
});
});

// Handle anchor clicks
document.querySelectorAll('.heading-anchor').forEach(anchor => {
anchor.addEventListener('click', (e) => {
e.preventDefault();
const targetId = anchor.getAttribute('href').slice(1);
const targetElement = document.getElementById(targetId);
// Highlight the section temporarily
targetElement.classList.add('highlight');
setTimeout(() => {
targetElement.classList.remove('highlight');
}, 1500);
}

if (targetElement) {
// Add URL fragment without scrolling
history.pushState(null, null, `#${targetId}`);
setupScrollSpy() {
const options = {
root: null,
rootMargin: '-100px 0px -66%',
threshold: 0
};

// Smooth scroll to element
targetElement.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
this.tocObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.updateActiveTocLink(entry.target);
}
});
}, options);

this.sections.forEach(section => {
this.tocObserver.observe(section);
});
}

// Visual feedback
targetElement.classList.add('highlight');
setTimeout(() => {
targetElement.classList.remove('highlight');
}, 1500);
updateActiveTocLink(section) {
this.tocLinks.forEach(link => {
link.classList.remove('active');
if (link.getAttribute('href') === `#${section.id}`) {
link.classList.add('active');
}
});
});
});
}

// Add CSS for anchor links
const style = document.createElement('style');
style.textContent = `
.heading-anchor {
opacity: 0;
margin-left: 0.5rem;
color: var(--platinum-grey);
transition: opacity 0.2s ease-in-out;
}
.heading-anchor:hover {
color: var(--blue);
}
h2:hover .heading-anchor,
h3:hover .heading-anchor,
h4:hover .heading-anchor,
h5:hover .heading-anchor,
h6:hover .heading-anchor {
opacity: 1;
}
.highlight {
animation: highlight 1.5s ease-out;
updateActiveSection() {
const scrollPosition = window.scrollY;

for (const section of this.sections) {
const sectionTop = section.offsetTop - 100;
const sectionBottom = sectionTop + section.offsetHeight;

if (scrollPosition >= sectionTop && scrollPosition < sectionBottom) {
if (this.currentSection !== section.id) {
this.currentSection = section.id;
this.updateActiveTocLink(section);
}
break;
}
}
@keyframes highlight {
0% { background-color: rgba(56, 119, 255, 0.2); }
100% { background-color: transparent; }
}

handleMobileNavState() {
const mediaQuery = window.matchMedia('(max-width: 768px)');

const handleMobileChange = (e) => {
if (!e.matches) {
// Reset mobile nav when returning to desktop
this.navLinks.classList.remove('active');
this.navToggle.setAttribute('aria-expanded', 'false');
}
};

mediaQuery.addListener(handleMobileChange);
}
}

// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
new RunbookManager();
});

// Add copy functionality to code blocks
document.querySelectorAll('pre code').forEach((block) => {
const copyButton = document.createElement('button');
copyButton.className = 'copy-button';
copyButton.textContent = 'Copy';

block.parentNode.style.position = 'relative';
block.parentNode.appendChild(copyButton);

copyButton.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(block.textContent);
copyButton.textContent = 'Copied!';
copyButton.classList.add('copied');

setTimeout(() => {
copyButton.textContent = 'Copy';
copyButton.classList.remove('copied');
}, 2000);
} catch (err) {
console.error('Failed to copy text:', err);
copyButton.textContent = 'Failed to copy';
copyButton.classList.add('error');

setTimeout(() => {
copyButton.textContent = 'Copy';
copyButton.classList.remove('error');
}, 2000);
}
`;
});
});

document.head.appendChild(style);
// Add support for deep linking
window.addEventListener('hashchange', () => {
if (window.location.hash) {
const targetElement = document.querySelector(window.location.hash);
if (targetElement) {
setTimeout(() => {
targetElement.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}, 0);
}
}
});

0 comments on commit 2c6812e

Please sign in to comment.