1257 lines · 50.3 KB
Raw Download
1
<?php
2
// index.php
3
date_default_timezone_set('America/Chicago');
4
$start = hrtime(true);
5
6
// require_once 'security.php';
7
require_once 'functions.php';
8
require_once 'parse_colors.php';
9
require_once 'process_form.php';
10
require_once 'download_report.php';
11
12
$parsed_colors = [];
13
$invalid_colors = [];
14
$advanced_mode = false;
15
$light_mode_bg_colors = [];
16
$light_mode_fg_colors = [];
17
$dark_mode_bg_colors = [];
18
$dark_mode_fg_colors = [];
19
$light_mode_invalid = [];
20
$dark_mode_invalid = [];
21
$light_mode_excess = false;
22
$dark_mode_excess = false;
23
$light_mode_duplicates = [];
24
$dark_mode_duplicates = [];
25
$light_mode_semantic_duplicates = [];
26
$dark_mode_semantic_duplicates = [];
27
28
// Handle form submission
29
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
30
	$advanced_mode = isset($_POST['advanced_mode']) && $_POST['advanced_mode'] === '1';
31
	$contrast_method = $_POST['contrast_method'] ?? 'wcag';
32
	
33
	if (isset($_POST['download'])) {
34
		if ($advanced_mode && (!empty($_POST['light_text']) || !empty($_POST['light_primary']) || !empty($_POST['light_bg']) || !empty($_POST['dark_text']) || !empty($_POST['dark_primary']) || !empty($_POST['dark_bg']))) {
35
			// Advanced mode download
36
			$advanced_result = processAdvancedModeColors(
37
				$_POST['light_text'] ?? '',
38
				$_POST['light_primary'] ?? '',
39
				$_POST['light_bg'] ?? '',
40
				$_POST['dark_text'] ?? '',
41
				$_POST['dark_primary'] ?? '',
42
				$_POST['dark_bg'] ?? ''
43
			);
44
			$light_mode_bg_colors = $advanced_result['light_mode']['background_colors'];
45
			$light_mode_fg_colors = $advanced_result['light_mode']['foreground_colors'];
46
			$dark_mode_bg_colors = $advanced_result['dark_mode']['background_colors'];
47
			$dark_mode_fg_colors = $advanced_result['dark_mode']['foreground_colors'];
48
			
49
			// Generate combined report
50
			$timestamp = date('Ymd-His');
51
			$method_suffix = $contrast_method === 'apca' ? '-apca' : ($contrast_method === 'both' ? '-both' : '-wcag');
52
			$filename = "color-contrast-report-advanced{$method_suffix}-{$timestamp}.html";
53
			
54
			header('Content-Type: text/html');
55
			header('Content-Disposition: attachment; filename="' . $filename . '"');
56
			
57
			// Generate report with both modes
58
			ob_start();
59
			include 'report_template_advanced.php';
60
			echo ob_get_clean();
61
			exit;
62
		} elseif (!empty($_POST['colors'])) {
63
			$colors = $_POST['colors'];
64
			$report = generateColorReport($colors, $contrast_method);
65
			
66
			$timestamp = date('Ymd-His');
67
			$method_suffix = $contrast_method === 'apca' ? '-apca' : ($contrast_method === 'both' ? '-both' : '-wcag');
68
			$filename = "color-contrast-report{$method_suffix}-{$timestamp}.html";
69
			
70
			header('Content-Type: text/html');
71
			header('Content-Disposition: attachment; filename="' . $filename . '"');
72
			
73
			echo $report;
74
			exit;
75
		}
76
	} elseif ($advanced_mode && (!empty($_POST['light_text']) || !empty($_POST['light_primary']) || !empty($_POST['light_bg']) || !empty($_POST['dark_text']) || !empty($_POST['dark_primary']) || !empty($_POST['dark_bg']))) {
77
		// Advanced mode processing
78
		$advanced_result = processAdvancedModeColors(
79
			$_POST['light_text'] ?? '',
80
			$_POST['light_primary'] ?? '',
81
			$_POST['light_bg'] ?? '',
82
			$_POST['dark_text'] ?? '',
83
			$_POST['dark_primary'] ?? '',
84
			$_POST['dark_bg'] ?? ''
85
		);
86
		$light_mode_bg_colors = $advanced_result['light_mode']['background_colors'];
87
		$light_mode_fg_colors = $advanced_result['light_mode']['foreground_colors'];
88
		$dark_mode_bg_colors = $advanced_result['dark_mode']['background_colors'];
89
		$dark_mode_fg_colors = $advanced_result['dark_mode']['foreground_colors'];
90
		$light_mode_invalid = $advanced_result['light_mode']['invalid_colors'];
91
		$dark_mode_invalid = $advanced_result['dark_mode']['invalid_colors'];
92
		$light_mode_excess = $advanced_result['light_mode']['excess_colors'];
93
		$dark_mode_excess = $advanced_result['dark_mode']['excess_colors'];
94
		$light_mode_duplicates = $advanced_result['light_mode']['duplicate_colors'];
95
		$dark_mode_duplicates = $advanced_result['dark_mode']['duplicate_colors'];
96
		$light_mode_semantic_duplicates = $advanced_result['light_mode']['semantic_duplicates'];
97
		$dark_mode_semantic_duplicates = $advanced_result['dark_mode']['semantic_duplicates'];
98
	} elseif (!empty($_POST['colors'])) {
99
		// Normal mode processing
100
		$result = processColorForm($_POST['colors']);
101
		$parsed_colors = $result['parsed_colors'];
102
		$excess_colors = $result['excess_colors'];
103
		$invalid_colors = $result['invalid_colors'];
104
		$duplicate_colors = $result['duplicate_colors'];
105
		$semantic_duplicates = $result['semantic_duplicates'];
106
	}
107
}
108
?>
109
<!DOCTYPE html>
110
<html lang="en">
111
<head>
112
	<meta charset="UTF-8">
113
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
114
	<title>Jeff's Color Contrast Analyzer</title>
115
	<link rel="stylesheet" href="style.css">
116
	<link rel="apple-touch-icon" sizes="180x180" href="https://contrast.jefftml.com/apple-touch-icon.png">
117
	<link rel="icon" type="image/png" sizes="32x32" href="https://contrast.jefftml.com/favicon-32x32.png">
118
	<link rel="icon" type="image/png" sizes="16x16" href="https://contrast.jefftml.com/favicon-16x16.png">
119
	<link rel="manifest" href="https://contrast.jefftml.com/site.webmanifest">
120
	<meta name="description" content="Calculate contrast ratios for multiple colors for WCAG and APCA guidelines. Hex, RGB, HSL, and all CSS color types supported. Download reports for offline use for free.">
121
</head>
122
<body>
123
	<nav aria-label="Main">
124
		<input type="checkbox" id="toggle" class="menu-toggle">
125
		<label for="toggle" class="menu-label">Menu</label>
126
		<ul class="topnav controlled">
127
			<li><a class="active" href="https://contrast.jefftml.com/">Home</a></li>
128
			<li><a href="https://contrast.jefftml.com/help.html">Help</a></li>
129
		</ul>
130
	</nav>
131
	<main>
132
	<div class="content">
133
	<h1>Jeff&rsquo;s Color Contrast Analyzer</h1>
134
	<form method="post">
135
		<p>Analyze color contrast between multiple colors, up to <?php echo MAX_COLORS; ?> colors total, in tons of formats. Hex, RGB and RGBA, HSL and HSLA, CSS Named Colors, HSB, HWB, Lab, LCH, Oklab, Oklch, and CMYK are fully supported. CSS syntax is also accepted <code>color: #FFF</code> or <code>background-color: rgb(255, 0, 0);</code>. <a href="https://contrast.jefftml.com/help.html#color-formats">More details and specifications</a> are included in the help documentation.</p>
136
		<p><code>from</code>, <code>calc()</code>, and <code>color()</code> are not supported. Labels can be added to a color by placing the label before a colon, like <code>link: #2C5491</code></p>
137
		
138
		<div style="margin: 1em 0;">
139
			<label style="display: block; margin: 0.5em 0;">
140
				<input type="checkbox" name="advanced_mode" id="advanced_mode" value="1" <?= (isset($_POST['advanced_mode']) && $_POST['advanced_mode'] === '1') ? 'checked' : '' ?> onchange="toggleAdvancedMode()"> Enable Advanced Mode (Light/Dark Mode)
141
			</label>
142
		</div>
143
		
144
		<div id="normal_mode_input" style="<?= (isset($_POST['advanced_mode']) && $_POST['advanced_mode'] === '1') ? 'display: none;' : '' ?>">
145
			<label for="colors">Input your colors:<br><textarea name="colors" id="colors" <?= (!isset($_POST['advanced_mode']) || $_POST['advanced_mode'] !== '1') ? 'required' : '' ?> autocapitalize="off" autocomplete="off" spellcheck="false" placeholder="Enter colors here, 
146
one color per line"><?= isset($_POST['colors']) ? htmlspecialchars($_POST['colors']) : '' ?></textarea></label>
147
		</div>
148
		
149
		<div id="advanced_mode_inputs" style="<?= (isset($_POST['advanced_mode']) && $_POST['advanced_mode'] === '1') ? '' : 'display: none;' ?>">
150
			<fieldset style="margin: 1em 0; border: 1px solid hsla(0, 0%, 50%, 0.3); padding: 1em;">
151
				<legend style="padding: 0 0.5em;">Light Mode Colors</legend>
152
				<label for="light_text" style="display: block; margin: 0.5em 0;">
153
					Text Colors (one per line):<br><textarea name="light_text" id="light_text" autocapitalize="off" autocomplete="off" spellcheck="false" placeholder="e.g., #000000&#10;#1A1A1A" style="width: 100%; min-height: 60px;"><?= isset($_POST['light_text']) ? htmlspecialchars($_POST['light_text']) : '' ?></textarea>
154
				</label>
155
				<label for="light_primary" style="display: block; margin: 0.5em 0;">
156
					Primary Colors (buttons/links, one per line):<br><textarea name="light_primary" id="light_primary" autocapitalize="off" autocomplete="off" spellcheck="false" placeholder="e.g., #0066CC&#10;#0055AA" style="width: 100%; min-height: 60px;"><?= isset($_POST['light_primary']) ? htmlspecialchars($_POST['light_primary']) : '' ?></textarea>
157
				</label>
158
				<label for="light_bg" style="display: block; margin: 0.5em 0;">
159
					Background Colors (one per line):<br><textarea name="light_bg" id="light_bg" autocapitalize="off" autocomplete="off" spellcheck="false" placeholder="e.g., #FFFFFF&#10;#F5F5F5" style="width: 100%; min-height: 60px;"><?= isset($_POST['light_bg']) ? htmlspecialchars($_POST['light_bg']) : '' ?></textarea>
160
				</label>
161
			</fieldset>
162
			
163
			<fieldset style="margin: 1em 0; border: 1px solid hsla(0, 0%, 50%, 0.3); padding: 1em;">
164
				<legend style="padding: 0 0.5em;">Dark Mode Colors</legend>
165
				<label for="dark_text" style="display: block; margin: 0.5em 0;">
166
					Text Colors (one per line):<br><textarea name="dark_text" id="dark_text" autocapitalize="off" autocomplete="off" spellcheck="false" placeholder="e.g., #FFFFFF&#10;#F5F5F5" style="width: 100%; min-height: 60px;"><?= isset($_POST['dark_text']) ? htmlspecialchars($_POST['dark_text']) : '' ?></textarea>
167
				</label>
168
				<label for="dark_primary" style="display: block; margin: 0.5em 0;">
169
					Primary Colors (buttons/links, one per line):<br><textarea name="dark_primary" id="dark_primary" autocapitalize="off" autocomplete="off" spellcheck="false" placeholder="e.g., #66B3FF&#10;#5599DD" style="width: 100%; min-height: 60px;"><?= isset($_POST['dark_primary']) ? htmlspecialchars($_POST['dark_primary']) : '' ?></textarea>
170
				</label>
171
				<label for="dark_bg" style="display: block; margin: 0.5em 0;">
172
					Background Colors (one per line):<br><textarea name="dark_bg" id="dark_bg" autocapitalize="off" autocomplete="off" spellcheck="false" placeholder="e.g., #000000&#10;#1A1A1A" style="width: 100%; min-height: 60px;"><?= isset($_POST['dark_bg']) ? htmlspecialchars($_POST['dark_bg']) : '' ?></textarea>
173
				</label>
174
			</fieldset>
175
		</div>
176
		
177
		<div style="margin: 1em 0;">
178
			<fieldset style="margin: 0; border: 1px solid hsla(0, 0%, 50%, 0.3); padding: 1em; max-width: 400px;">
179
				<legend style="padding: 0 0.5em;">Contrast Method:</legend>
180
				<label style="display: block; margin: 0.5em 0;">
181
					<input type="radio" name="contrast_method" value="wcag" <?= (!isset($contrast_method) || $contrast_method === 'wcag') ? 'checked' : '' ?>> WCAG - current standard
182
				</label>
183
				<label style="display: block; margin: 0.5em 0;">
184
					<input type="radio" name="contrast_method" value="apca" <?= (isset($contrast_method) && $contrast_method === 'apca') ? 'checked' : '' ?>> APCA - potential standard
185
				</label>
186
				<label style="display: block; margin: 0.5em 0;">
187
					<input type="radio" name="contrast_method" value="both" <?= (isset($contrast_method) && $contrast_method === 'both') ? 'checked' : '' ?>> Best of Both - combined results
188
				</label>
189
			</fieldset>
190
		</div>
191
		<button type="submit">Calculate Contrast</button>
192
	</form>
193
	<div id="results"></div>
194
<?php if (!empty($parsed_colors)): ?>
195
	<div style="margin-top: 1em;"><form method="post">
196
		<input type="hidden" name="colors" value="<?= htmlspecialchars($_POST['colors']) ?>">
197
		<input type="hidden" name="contrast_method" value="<?= htmlspecialchars($contrast_method ?? 'wcag') ?>">
198
		<button type="submit" name="download" value="1">Download Report</button>
199
	</form></div>
200
<?php endif;
201
if ($advanced_mode && (!empty($light_mode_bg_colors) || !empty($dark_mode_bg_colors))): ?>
202
	<div style="margin-top: 1em;"><form method="post">
203
		<input type="hidden" name="advanced_mode" value="1">
204
		<input type="hidden" name="light_text" value="<?= htmlspecialchars($_POST['light_text'] ?? '') ?>">
205
		<input type="hidden" name="light_primary" value="<?= htmlspecialchars($_POST['light_primary'] ?? '') ?>">
206
		<input type="hidden" name="light_bg" value="<?= htmlspecialchars($_POST['light_bg'] ?? '') ?>">
207
		<input type="hidden" name="dark_text" value="<?= htmlspecialchars($_POST['dark_text'] ?? '') ?>">
208
		<input type="hidden" name="dark_primary" value="<?= htmlspecialchars($_POST['dark_primary'] ?? '') ?>">
209
		<input type="hidden" name="dark_bg" value="<?= htmlspecialchars($_POST['dark_bg'] ?? '') ?>">
210
		<input type="hidden" name="contrast_method" value="<?= htmlspecialchars($contrast_method ?? 'wcag') ?>">
211
		<button type="submit" name="download" value="1">Download Report</button>
212
	</form></div>
213
<?php endif;
214
if (!empty($excess_colors)): ?>
215
	<div class="error-message">
216
		<h3>Too Many Colors</h3>
217
		<p>Only the first <?php echo MAX_COLORS; ?> colors were processed. Please reduce the number of colors in your input.</p>
218
	</div>
219
	<br>
220
<?php endif;
221
if (!empty($invalid_colors)): ?>
222
	<div class="error-message">
223
		<h3>Invalid Colors</h3>
224
		<p>The following colors could not be parsed.</p>
225
		<ul class="error-list">
226
			<?php foreach ($invalid_colors as $color): ?>
227
			<li><code><?= htmlspecialchars($color) ?></code></li>
228
			<?php endforeach; ?>
229
		</ul>
230
	</div>
231
	<br>
232
<?php endif;
233
if (!empty($duplicate_colors)): ?>
234
	<div class="warning-message">
235
		<h3>Duplicate Colors</h3>
236
		<p>The following colors were entered more than once. Only the first occurrence was processed.</p>
237
		<ul class="error-list">
238
			<?php foreach ($duplicate_colors as $color): ?>
239
			<li><code><?= htmlspecialchars($color) ?></code></li>
240
			<?php endforeach; ?>
241
		</ul>
242
	</div>
243
	<br>
244
<?php endif;
245
if (!empty($semantic_duplicates)): ?>
246
	<div class="warning-message">
247
		<h3>Equivalent Colors</h3>
248
		<p>The following colors represent the same values in different formats. Only the first occurrence was processed.</p>
249
		<ul class="warning-list">
250
			<?php foreach ($semantic_duplicates as $group): ?>
251
				<li>
252
					Original: <code><?= htmlspecialchars($group['original']) ?></code>
253
					<br>
254
					Equivalent formats:
255
						<?= implode(', ', array_map(function($duplicate) {
256
							return '<code>' . htmlspecialchars($duplicate) . '</code>';
257
						}, $group['duplicates'])) ?>
258
				</li>
259
			<?php endforeach; ?>
260
		</ul>
261
	</div>
262
	<br>
263
<?php endif;
264
// Advanced mode error/warning messages
265
if ($advanced_mode && (!empty($light_mode_bg_colors) || !empty($dark_mode_bg_colors))): 
266
	if ($light_mode_excess || $dark_mode_excess): ?>
267
		<div class="error-message">
268
			<h3>Too Many Colors</h3>
269
			<p>Only the first <?php echo MAX_COLORS; ?> colors were processed in some modes. Please reduce the number of colors in your input.</p>
270
		</div>
271
		<br>
272
	<?php endif;
273
	if (!empty($light_mode_invalid) || !empty($dark_mode_invalid)): ?>
274
		<div class="error-message">
275
			<h3>Invalid Colors</h3>
276
			<?php if (!empty($light_mode_invalid)): ?>
277
				<p>Light mode - The following colors could not be parsed:</p>
278
				<ul class="error-list">
279
					<?php foreach ($light_mode_invalid as $color): ?>
280
					<li><code><?= htmlspecialchars($color) ?></code></li>
281
					<?php endforeach; ?>
282
				</ul>
283
			<?php endif;
284
			if (!empty($dark_mode_invalid)): ?>
285
				<p>Dark mode - The following colors could not be parsed:</p>
286
				<ul class="error-list">
287
					<?php foreach ($dark_mode_invalid as $color): ?>
288
					<li><code><?= htmlspecialchars($color) ?></code></li>
289
					<?php endforeach; ?>
290
				</ul>
291
			<?php endif; ?>
292
		</div>
293
		<br>
294
	<?php endif;
295
	if (!empty($light_mode_duplicates) || !empty($dark_mode_duplicates)): ?>
296
		<div class="warning-message">
297
			<h3>Duplicate Colors</h3>
298
			<p>The following colors were entered more than once. Only the first occurrence was processed.</p>
299
			<?php if (!empty($light_mode_duplicates)): ?>
300
				<p>Light mode:</p>
301
				<ul class="error-list">
302
					<?php foreach ($light_mode_duplicates as $color): ?>
303
					<li><code><?= htmlspecialchars($color) ?></code></li>
304
					<?php endforeach; ?>
305
				</ul>
306
			<?php endif;
307
			if (!empty($dark_mode_duplicates)): ?>
308
				<p>Dark mode:</p>
309
				<ul class="error-list">
310
					<?php foreach ($dark_mode_duplicates as $color): ?>
311
					<li><code><?= htmlspecialchars($color) ?></code></li>
312
					<?php endforeach; ?>
313
				</ul>
314
			<?php endif; ?>
315
		</div>
316
		<br>
317
	<?php endif;
318
	if (!empty($light_mode_semantic_duplicates) || !empty($dark_mode_semantic_duplicates)): ?>
319
		<div class="warning-message">
320
			<h3>Equivalent Colors</h3>
321
			<p>The following colors represent the same values in different formats. Only the first occurrence was processed.</p>
322
			<?php if (!empty($light_mode_semantic_duplicates)): ?>
323
				<p>Light mode:</p>
324
				<ul class="warning-list">
325
					<?php foreach ($light_mode_semantic_duplicates as $group): ?>
326
						<li>
327
							Original: <code><?= htmlspecialchars($group['original']) ?></code>
328
							<br>
329
							Equivalent formats:
330
								<?= implode(', ', array_map(function($duplicate) {
331
									return '<code>' . htmlspecialchars($duplicate) . '</code>';
332
								}, $group['duplicates'])) ?>
333
						</li>
334
					<?php endforeach; ?>
335
				</ul>
336
			<?php endif;
337
			if (!empty($dark_mode_semantic_duplicates)): ?>
338
				<p>Dark mode:</p>
339
				<ul class="warning-list">
340
					<?php foreach ($dark_mode_semantic_duplicates as $group): ?>
341
						<li>
342
							Original: <code><?= htmlspecialchars($group['original']) ?></code>
343
							<br>
344
							Equivalent formats:
345
								<?= implode(', ', array_map(function($duplicate) {
346
									return '<code>' . htmlspecialchars($duplicate) . '</code>';
347
								}, $group['duplicates'])) ?>
348
						</li>
349
					<?php endforeach; ?>
350
				</ul>
351
			<?php endif; ?>
352
		</div>
353
		<br>
354
	<?php endif;
355
endif;
356
// Normal mode results
357
if (!$advanced_mode && !empty($parsed_colors)): 
358
	// log a usage for our stats.txt
359
	date_default_timezone_set('America/Chicago');
360
	$datetime = date('Y-m-d h:i:s A');
361
	$file = 'stats.txt';
362
363
	// Get IP address (handles proxies too)
364
	$ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? 'unknown';
365
366
	// Get the actual colors (extract just the color values, not labels)
367
	$color_list = array_keys($parsed_colors);
368
	$colors_string = implode('|', array_map('htmlspecialchars', $color_list));
369
370
	$value = count($parsed_colors)." colors, ".($contrast_method ?? 'wcag')." method, IP: ".$ip_address.", Colors: ".$colors_string;
371
372
	// Create the log entry
373
	$logEntry = $datetime . ", " . $value . PHP_EOL;
374
	file_put_contents($file, $logEntry, FILE_APPEND);
375
	
376
	$current_method = $contrast_method ?? 'wcag';
377
	?>
378
	<div class="table-wrapper">
379
		<h2>Summary of Compatible Color Combinations (<?= $current_method === 'both' ? 'Both' : strtoupper($current_method) ?>)</h2>
380
		<table class="checkered">
381
			<thead>
382
				<tr>
383
					<th>Background</th>
384
					<?php if ($current_method === 'wcag'): ?>
385
						<th>AAA ≥ 7.0<br>Best</th>
386
						<th>AA Normal ≥ 4.5<br>Second Best</th>
387
						<th>AA Large ≥ 3.0<br>Third Best</th>
388
					<?php elseif ($current_method === 'apca'): ?>
389
						<th>Perfect ≥ 90</th>
390
						<th>Excellent ≥ 75</th>
391
						<th>Good ≥ 60</th>
392
						<th>Fair ≥ 45</th>
393
					<?php else: // both ?>
394
						<th>Perfect<br>WCAG&nbsp;≥10&nbsp;&<br>APCA&nbsp;≥90</th>
395
						<th>Excellent<br>WCAG&nbsp;≥7&nbsp;&<br>APCA&nbsp;≥75</th>
396
						<th>Good<br>WCAG&nbsp;≥4.5&nbsp;&<br>APCA&nbsp;≥60</th>
397
						<th>Fair<br>WCAG&nbsp;≥3&nbsp;&<br>APCA&nbsp;≥45</th>
398
					<?php endif; ?>
399
				</tr>
400
			</thead>
401
			<tbody>
402
				<?php foreach ($parsed_colors as $bg_color => $bg): 
403
					// Calculate all combinations for this background color
404
					$combinations = [];
405
					foreach ($parsed_colors as $fg_color => $fg) {
406
						if ($fg_color === $bg_color) continue;
407
						
408
						if ($current_method === 'wcag') {
409
							// Calculate luminance with alpha blending
410
							$fg_lum = getLuminance($fg['rgb'], $fg['alpha'], $bg['rgb']);
411
							$bg_lum = $bg['luminance'];
412
							$contrast = getContrastRatio($bg_lum, $fg_lum);
413
							$level = getWCAGLevel($contrast);
414
							
415
							if ($level !== 'Fail') {
416
								$combinations[] = [
417
									'color' => $fg_color,
418
									'contrast' => $contrast,
419
									'level' => $level
420
								];
421
							}
422
						} elseif ($current_method === 'apca') {
423
							// APCA calculations
424
							$contrast = getAPCAContrast($bg['rgb'], $fg['rgb'], $bg['alpha'], $fg['alpha']);
425
							$level = getAPCALevel($contrast);
426
							
427
							if (!str_contains($level, 'Fail')) {
428
								$combinations[] = [
429
									'color' => $fg_color,
430
									'contrast' => $contrast,
431
									'level' => $level
432
								];
433
							}
434
						} else { // both
435
							// Calculate both WCAG and APCA
436
							$fg_lum = getLuminance($fg['rgb'], $fg['alpha'], $bg['rgb']);
437
							$bg_lum = $bg['luminance'];
438
							$wcag_contrast = getContrastRatio($bg_lum, $fg_lum);
439
							$wcag_level = getWCAGLevel($wcag_contrast);
440
							
441
							$apca_contrast = getAPCAContrast($bg['rgb'], $fg['rgb'], $bg['alpha'], $fg['alpha']);
442
							$apca_level = getAPCALevel($apca_contrast);
443
							
444
							$combined_level = getCombinedLevel($wcag_contrast, $apca_contrast);
445
							
446
							if ($combined_level !== 'Fail') {
447
								$combinations[] = [
448
									'color' => $fg_color,
449
									'wcag_contrast' => $wcag_contrast,
450
									'apca_contrast' => $apca_contrast,
451
									'level' => $combined_level
452
								];
453
							}
454
						}
455
					}
456
457
					if ($current_method === 'wcag') {
458
						// Group by WCAG level
459
						$wcag_groups = [
460
							'AAA' => [],
461
							'AA' => [],
462
							'AA Large' => []
463
						];
464
						
465
						foreach ($combinations as $combo) {
466
							$wcag_groups[$combo['level']][] = $combo;
467
						}
468
						
469
						// Sort each group by contrast ratio
470
						foreach ($wcag_groups as &$group) {
471
							usort($group, function($a, $b) {
472
								return $b['contrast'] <=> $a['contrast'];
473
							});
474
						}
475
476
						$has_valid_combinations = !empty($wcag_groups['AAA']) || 
477
												!empty($wcag_groups['AA']) || 
478
												!empty($wcag_groups['AA Large']);
479
					} elseif ($current_method === 'apca') {
480
						// Group by APCA level
481
						$apca_groups = [
482
							'Perfect' => [],
483
							'Excellent' => [],
484
							'Good' => [],
485
							'Fair' => []
486
						];
487
						
488
						foreach ($combinations as $combo) {
489
							$level_key = explode(' - ', $combo['level'])[0];
490
							if (isset($apca_groups[$level_key])) {
491
								$apca_groups[$level_key][] = $combo;
492
							}
493
						}
494
						
495
						// Sort each group by absolute contrast value
496
						foreach ($apca_groups as &$group) {
497
							usort($group, function($a, $b) {
498
								return abs($b['contrast']) <=> abs($a['contrast']);
499
							});
500
						}
501
502
						$has_valid_combinations = !empty($apca_groups['Perfect']) || 
503
												!empty($apca_groups['Excellent']) || 
504
												!empty($apca_groups['Good']) || 
505
												!empty($apca_groups['Fair']);
506
					} else { // both
507
						// Group by combined level
508
						$combined_groups = [
509
							'Perfect' => [],
510
							'Excellent' => [],
511
							'Good' => [],
512
							'Fair' => []
513
						];
514
						
515
						foreach ($combinations as $combo) {
516
							if (isset($combined_groups[$combo['level']])) {
517
								$combined_groups[$combo['level']][] = $combo;
518
							}
519
						}
520
						
521
						// Sort each group by WCAG contrast ratio
522
						foreach ($combined_groups as &$group) {
523
							usort($group, function($a, $b) {
524
								return $b['wcag_contrast'] <=> $a['wcag_contrast'];
525
							});
526
						}
527
528
						$has_valid_combinations = !empty($combined_groups['Perfect']) || 
529
												!empty($combined_groups['Excellent']) || 
530
												!empty($combined_groups['Good']) || 
531
												!empty($combined_groups['Fair']);
532
					}
533
				?>
534
				<tr style="background-color: <?= htmlspecialchars(getCssColor($bg_color)) ?>;">
535
					<td>
536
						<?php 
537
						// Use alpha-aware luminance for text color calculation
538
						$bg_text_lum = getLuminance($bg['rgb'], $bg['alpha']); ?>
539
						<span style="color: <?= $bg_text_lum > 0.5 ? '#000000' : '#FFFFFF' ?>"><?= htmlspecialchars(getCleanColorName($bg_color)) ?></span>
540
					</td>
541
					<?php if ($has_valid_combinations): ?>
542
						<?php if ($current_method === 'wcag'): ?>
543
							<?php foreach (['AAA', 'AA', 'AA Large'] as $level): ?>
544
								<td><?php foreach ($wcag_groups[$level] as $combo): ?>
545
									<div style="color: <?= htmlspecialchars(getCssColor($combo['color'])) ?>;"><?= htmlspecialchars($combo['color']) ?>&nbsp;(Ratio:&nbsp;<?= number_format($combo['contrast'], 2) ?>)</div>
546
								<?php endforeach; ?></td>
547
							<?php endforeach; ?>
548
						<?php elseif ($current_method === 'apca'): ?>
549
							<?php foreach (['Perfect', 'Excellent', 'Good', 'Fair'] as $level): ?>
550
								<td><?php foreach ($apca_groups[$level] as $combo): ?>
551
									<div style="color: <?= htmlspecialchars(getCssColor($combo['color'])) ?>;"><?= htmlspecialchars($combo['color']) ?>&nbsp;(Lc:&nbsp;<?= number_format($combo['contrast'], 1) ?>)</div>
552
								<?php endforeach; ?></td>
553
							<?php endforeach; ?>
554
						<?php else: // both ?>
555
							<?php foreach (['Perfect', 'Excellent', 'Good', 'Fair'] as $level): ?>
556
								<td><?php foreach ($combined_groups[$level] as $combo): ?>
557
									<div style="color: <?= htmlspecialchars(getCssColor($combo['color'])) ?>;"><?= htmlspecialchars($combo['color']) ?>&nbsp;(<?= number_format($combo['wcag_contrast'], 2) ?>)&nbsp;(Lc&nbsp;<?= number_format($combo['apca_contrast'], 1) ?>)</div>
558
								<?php endforeach; ?></td>
559
							<?php endforeach; ?>
560
						<?php endif; ?>
561
					<?php else: ?>
562
						<td colspan="<?= $current_method === 'wcag' ? '3' : '4' ?>" style="text-align: center; color: <?= $bg_text_lum > 0.5 ? '#000000' : '#FFFFFF' ?>">
563
							<?php if ($current_method === 'wcag'): ?>
564
								No valid color combinations found (all contrast ratios below 3.0)
565
							<?php elseif ($current_method === 'apca'): ?>
566
								No valid color combinations found (all APCA values below 45)
567
							<?php else: ?>
568
								No valid color combinations found (failed both WCAG and APCA thresholds)
569
							<?php endif; ?>
570
						</td>
571
					<?php endif; ?>
572
				</tr>
573
				<?php endforeach; ?>
574
			</tbody>
575
		</table>
576
	</div>
577
578
	<div class="table-wrapper">
579
	<h2>Complete Contrast Grid (<?= $current_method === 'both' ? 'Both' : strtoupper($current_method) ?>)</h2>
580
	<table class="checkered">
581
	<tr>
582
		<th>BG \ FG</th>
583
	<?php foreach ($parsed_colors as $color => $data): ?>
584
		<th><?= htmlspecialchars(getCleanColorName($color)) ?></th>
585
	<?php endforeach; ?>
586
	</tr>
587
	<?php foreach ($parsed_colors as $bg_color => $bg_data): ?>
588
	<tr>
589
		<th><?= htmlspecialchars(getCleanColorName($bg_color)) ?></th>
590
		<?php foreach ($parsed_colors as $fg_color => $fg_data): 
591
			if ($current_method === 'wcag') {
592
				// Calculate luminance with alpha blending
593
				$fg_lum = getLuminance($fg_data['rgb'], $fg_data['alpha'], $bg_data['rgb']);
594
				$bg_lum = $bg_data['luminance'];
595
				$contrast = getContrastRatio($bg_lum, $fg_lum);
596
				$level = getWCAGLevel($contrast);
597
				$display_value = number_format($contrast, 2);
598
			} elseif ($current_method === 'apca') {
599
				// APCA calculations
600
				$contrast = getAPCAContrast($bg_data['rgb'], $fg_data['rgb'], $bg_data['alpha'], $fg_data['alpha']);
601
				$level = getAPCALevel($contrast);
602
				$display_value = number_format($contrast, 1);
603
			} else { // both
604
				// Calculate both WCAG and APCA
605
				$fg_lum = getLuminance($fg_data['rgb'], $fg_data['alpha'], $bg_data['rgb']);
606
				$bg_lum = $bg_data['luminance'];
607
				$wcag_contrast = getContrastRatio($bg_lum, $fg_lum);
608
				$wcag_level = getWCAGLevel($wcag_contrast);
609
				
610
				$apca_contrast = getAPCAContrast($bg_data['rgb'], $fg_data['rgb'], $bg_data['alpha'], $fg_data['alpha']);
611
				$apca_level = getAPCALevel($apca_contrast);
612
				
613
				$combined_level = getCombinedLevel($wcag_contrast, $apca_contrast);
614
			}
615
		?>
616
			<td style="background-color: <?= htmlspecialchars(getCssColor($bg_color)) ?>;">
617
				<!-- color value hover / alt text? -->
618
				<div class="sample-text" style="color: <?= htmlspecialchars(getCssColor($fg_color)) ?>;">Sample
619
				<div style="font-size: 0.8em;">
620
					<?php if ($current_method === 'wcag'): ?>
621
						<?= $display_value ?><br><?= $level ?>
622
					<?php elseif ($current_method === 'apca'): ?>
623
						Lc&nbsp;<?= $display_value ?><br><?= explode(' - ', $level)[0] ?>
624
					<?php else: // both ?>
625
						(<?= number_format($wcag_contrast, 2) ?>) (Lc&nbsp;<?= number_format($apca_contrast, 1) ?>)<br><?= $combined_level ?>
626
					<?php endif; ?>
627
				</div>
628
				</div>
629
			</td>
630
	<?php endforeach; ?>
631
	</tr>
632
	<?php endforeach; ?>
633
	</table>
634
	</div>
635
<?php endif; 
636
// Advanced mode results - Light Mode
637
if ($advanced_mode && !empty($light_mode_bg_colors)): 
638
	$current_method = $contrast_method ?? 'wcag';
639
	$bg_colors = $light_mode_bg_colors;
640
	$fg_colors = $light_mode_fg_colors;
641
	?>
642
	<h2 style="margin-top: 2em;">Light Mode Results</h2>
643
	<div class="table-wrapper">
644
		<h2>Summary of Compatible Color Combinations (<?= $current_method === 'both' ? 'Both' : strtoupper($current_method) ?>)</h2>
645
		<table class="checkered">
646
			<thead>
647
				<tr>
648
					<th>Background</th>
649
					<?php if ($current_method === 'wcag'): ?>
650
						<th>AAA ≥ 7.0<br>Best</th>
651
						<th>AA Normal ≥ 4.5<br>Second Best</th>
652
						<th>AA Large ≥ 3.0<br>Third Best</th>
653
					<?php elseif ($current_method === 'apca'): ?>
654
						<th>Perfect ≥ 90</th>
655
						<th>Excellent ≥ 75</th>
656
						<th>Good ≥ 60</th>
657
						<th>Fair ≥ 45</th>
658
					<?php else: // both ?>
659
						<th>Perfect<br>WCAG&nbsp;≥10&nbsp;&<br>APCA&nbsp;≥90</th>
660
						<th>Excellent<br>WCAG&nbsp;≥7&nbsp;&<br>APCA&nbsp;≥75</th>
661
						<th>Good<br>WCAG&nbsp;≥4.5&nbsp;&<br>APCA&nbsp;≥60</th>
662
						<th>Fair<br>WCAG&nbsp;≥3&nbsp;&<br>APCA&nbsp;≥45</th>
663
					<?php endif; ?>
664
				</tr>
665
			</thead>
666
			<tbody>
667
				<?php foreach ($bg_colors as $bg_color => $bg): 
668
					// Calculate all combinations for this background color
669
					$combinations = [];
670
					foreach ($fg_colors as $fg_color => $fg) {
671
						
672
						if ($current_method === 'wcag') {
673
							// Calculate luminance with alpha blending
674
							$fg_lum = getLuminance($fg['rgb'], $fg['alpha'], $bg['rgb']);
675
							$bg_lum = $bg['luminance'];
676
							$contrast = getContrastRatio($bg_lum, $fg_lum);
677
							$level = getWCAGLevel($contrast);
678
							
679
							if ($level !== 'Fail') {
680
								$combinations[] = [
681
									'color' => $fg_color,
682
									'contrast' => $contrast,
683
									'level' => $level
684
								];
685
							}
686
						} elseif ($current_method === 'apca') {
687
							// APCA calculations
688
							$contrast = getAPCAContrast($bg['rgb'], $fg['rgb'], $bg['alpha'], $fg['alpha']);
689
							$level = getAPCALevel($contrast);
690
							
691
							if (!str_contains($level, 'Fail')) {
692
								$combinations[] = [
693
									'color' => $fg_color,
694
									'contrast' => $contrast,
695
									'level' => $level
696
								];
697
							}
698
						} else { // both
699
							// Calculate both WCAG and APCA
700
							$fg_lum = getLuminance($fg['rgb'], $fg['alpha'], $bg['rgb']);
701
							$bg_lum = $bg['luminance'];
702
							$wcag_contrast = getContrastRatio($bg_lum, $fg_lum);
703
							$wcag_level = getWCAGLevel($wcag_contrast);
704
							
705
							$apca_contrast = getAPCAContrast($bg['rgb'], $fg['rgb'], $bg['alpha'], $fg['alpha']);
706
							$apca_level = getAPCALevel($apca_contrast);
707
							
708
							$combined_level = getCombinedLevel($wcag_contrast, $apca_contrast);
709
							
710
							if ($combined_level !== 'Fail') {
711
								$combinations[] = [
712
									'color' => $fg_color,
713
									'wcag_contrast' => $wcag_contrast,
714
									'apca_contrast' => $apca_contrast,
715
									'level' => $combined_level
716
								];
717
							}
718
						}
719
					}
720
721
					if ($current_method === 'wcag') {
722
						// Group by WCAG level
723
						$wcag_groups = [
724
							'AAA' => [],
725
							'AA' => [],
726
							'AA Large' => []
727
						];
728
						
729
						foreach ($combinations as $combo) {
730
							$wcag_groups[$combo['level']][] = $combo;
731
						}
732
						
733
						// Sort each group by contrast ratio
734
						foreach ($wcag_groups as &$group) {
735
							usort($group, function($a, $b) {
736
								return $b['contrast'] <=> $a['contrast'];
737
							});
738
						}
739
740
						$has_valid_combinations = !empty($wcag_groups['AAA']) || 
741
												!empty($wcag_groups['AA']) || 
742
												!empty($wcag_groups['AA Large']);
743
					} elseif ($current_method === 'apca') {
744
						// Group by APCA level
745
						$apca_groups = [
746
							'Perfect' => [],
747
							'Excellent' => [],
748
							'Good' => [],
749
							'Fair' => []
750
						];
751
						
752
						foreach ($combinations as $combo) {
753
							$level_key = explode(' - ', $combo['level'])[0];
754
							if (isset($apca_groups[$level_key])) {
755
								$apca_groups[$level_key][] = $combo;
756
							}
757
						}
758
						
759
						// Sort each group by absolute contrast value
760
						foreach ($apca_groups as &$group) {
761
							usort($group, function($a, $b) {
762
								return abs($b['contrast']) <=> abs($a['contrast']);
763
							});
764
						}
765
766
						$has_valid_combinations = !empty($apca_groups['Perfect']) || 
767
												!empty($apca_groups['Excellent']) || 
768
												!empty($apca_groups['Good']) || 
769
												!empty($apca_groups['Fair']);
770
					} else { // both
771
						// Group by combined level
772
						$combined_groups = [
773
							'Perfect' => [],
774
							'Excellent' => [],
775
							'Good' => [],
776
							'Fair' => []
777
						];
778
						
779
						foreach ($combinations as $combo) {
780
							if (isset($combined_groups[$combo['level']])) {
781
								$combined_groups[$combo['level']][] = $combo;
782
							}
783
						}
784
						
785
						// Sort each group by WCAG contrast ratio
786
						foreach ($combined_groups as &$group) {
787
							usort($group, function($a, $b) {
788
								return $b['wcag_contrast'] <=> $a['wcag_contrast'];
789
							});
790
						}
791
792
						$has_valid_combinations = !empty($combined_groups['Perfect']) || 
793
												!empty($combined_groups['Excellent']) || 
794
												!empty($combined_groups['Good']) || 
795
												!empty($combined_groups['Fair']);
796
					}
797
				?>
798
				<tr style="background-color: <?= htmlspecialchars(getCssColor($bg_color)) ?>;">
799
					<td>
800
						<?php 
801
						// Use alpha-aware luminance for text color calculation
802
						$bg_text_lum = getLuminance($bg['rgb'], $bg['alpha']); ?>
803
						<span style="color: <?= $bg_text_lum > 0.5 ? '#000000' : '#FFFFFF' ?>"><?= htmlspecialchars(getCleanColorName($bg_color)) ?></span>
804
					</td>
805
					<?php if ($has_valid_combinations): ?>
806
						<?php if ($current_method === 'wcag'): ?>
807
							<?php foreach (['AAA', 'AA', 'AA Large'] as $level): ?>
808
								<td><?php foreach ($wcag_groups[$level] as $combo): ?>
809
									<div style="color: <?= htmlspecialchars(getCssColor($combo['color'])) ?>;"><?= htmlspecialchars($combo['color']) ?>&nbsp;(Ratio:&nbsp;<?= number_format($combo['contrast'], 2) ?>)</div>
810
								<?php endforeach; ?></td>
811
							<?php endforeach; ?>
812
						<?php elseif ($current_method === 'apca'): ?>
813
							<?php foreach (['Perfect', 'Excellent', 'Good', 'Fair'] as $level): ?>
814
								<td><?php foreach ($apca_groups[$level] as $combo): ?>
815
									<div style="color: <?= htmlspecialchars(getCssColor($combo['color'])) ?>;"><?= htmlspecialchars($combo['color']) ?>&nbsp;(Lc:&nbsp;<?= number_format($combo['contrast'], 1) ?>)</div>
816
								<?php endforeach; ?></td>
817
							<?php endforeach; ?>
818
						<?php else: // both ?>
819
							<?php foreach (['Perfect', 'Excellent', 'Good', 'Fair'] as $level): ?>
820
								<td><?php foreach ($combined_groups[$level] as $combo): ?>
821
									<div style="color: <?= htmlspecialchars(getCssColor($combo['color'])) ?>;"><?= htmlspecialchars($combo['color']) ?>&nbsp;(<?= number_format($combo['wcag_contrast'], 2) ?>)&nbsp;(Lc&nbsp;<?= number_format($combo['apca_contrast'], 1) ?>)</div>
822
								<?php endforeach; ?></td>
823
							<?php endforeach; ?>
824
						<?php endif; ?>
825
					<?php else: ?>
826
						<td colspan="<?= $current_method === 'wcag' ? '3' : '4' ?>" style="text-align: center; color: <?= $bg_text_lum > 0.5 ? '#000000' : '#FFFFFF' ?>">
827
							<?php if ($current_method === 'wcag'): ?>
828
								No valid color combinations found (all contrast ratios below 3.0)
829
							<?php elseif ($current_method === 'apca'): ?>
830
								No valid color combinations found (all APCA values below 45)
831
							<?php else: ?>
832
								No valid color combinations found (failed both WCAG and APCA thresholds)
833
							<?php endif; ?>
834
						</td>
835
					<?php endif; ?>
836
				</tr>
837
				<?php endforeach; ?>
838
			</tbody>
839
		</table>
840
	</div>
841
842
	<div class="table-wrapper">
843
	<h2>Complete Contrast Grid - Light Mode (<?= $current_method === 'both' ? 'Both' : strtoupper($current_method) ?>)</h2>
844
	<table class="checkered">
845
	<tr>
846
		<th>BG \ FG</th>
847
	<?php foreach ($fg_colors as $color => $data): ?>
848
		<th><?= htmlspecialchars(getCleanColorName($color)) ?></th>
849
	<?php endforeach; ?>
850
	</tr>
851
	<?php foreach ($bg_colors as $bg_color => $bg_data): ?>
852
	<tr>
853
		<th><?= htmlspecialchars(getCleanColorName($bg_color)) ?></th>
854
		<?php foreach ($fg_colors as $fg_color => $fg_data): 
855
			if ($current_method === 'wcag') {
856
				// Calculate luminance with alpha blending
857
				$fg_lum = getLuminance($fg_data['rgb'], $fg_data['alpha'], $bg_data['rgb']);
858
				$bg_lum = $bg_data['luminance'];
859
				$contrast = getContrastRatio($bg_lum, $fg_lum);
860
				$level = getWCAGLevel($contrast);
861
				$display_value = number_format($contrast, 2);
862
			} elseif ($current_method === 'apca') {
863
				// APCA calculations
864
				$contrast = getAPCAContrast($bg_data['rgb'], $fg_data['rgb'], $bg_data['alpha'], $fg_data['alpha']);
865
				$level = getAPCALevel($contrast);
866
				$display_value = number_format($contrast, 1);
867
			} else { // both
868
				// Calculate both WCAG and APCA
869
				$fg_lum = getLuminance($fg_data['rgb'], $fg_data['alpha'], $bg_data['rgb']);
870
				$bg_lum = $bg_data['luminance'];
871
				$wcag_contrast = getContrastRatio($bg_lum, $fg_lum);
872
				$wcag_level = getWCAGLevel($wcag_contrast);
873
				
874
				$apca_contrast = getAPCAContrast($bg_data['rgb'], $fg_data['rgb'], $bg_data['alpha'], $fg_data['alpha']);
875
				$apca_level = getAPCALevel($apca_contrast);
876
				
877
				$combined_level = getCombinedLevel($wcag_contrast, $apca_contrast);
878
			}
879
		?>
880
			<td style="background-color: <?= htmlspecialchars(getCssColor($bg_color)) ?>;">
881
				<!-- color value hover / alt text? -->
882
				<div class="sample-text" style="color: <?= htmlspecialchars(getCssColor($fg_color)) ?>;">Sample
883
				<div style="font-size: 0.8em;">
884
					<?php if ($current_method === 'wcag'): ?>
885
						<?= $display_value ?><br><?= $level ?>
886
					<?php elseif ($current_method === 'apca'): ?>
887
						Lc&nbsp;<?= $display_value ?><br><?= explode(' - ', $level)[0] ?>
888
					<?php else: // both ?>
889
						(<?= number_format($wcag_contrast, 2) ?>) (Lc&nbsp;<?= number_format($apca_contrast, 1) ?>)<br><?= $combined_level ?>
890
					<?php endif; ?>
891
				</div>
892
				</div>
893
			</td>
894
	<?php endforeach; ?>
895
	</tr>
896
	<?php endforeach; ?>
897
	</table>
898
	</div>
899
<?php endif; 
900
// Advanced mode results - Dark Mode
901
if ($advanced_mode && !empty($dark_mode_bg_colors)): 
902
	$current_method = $contrast_method ?? 'wcag';
903
	$bg_colors = $dark_mode_bg_colors;
904
	$fg_colors = $dark_mode_fg_colors;
905
	?>
906
	<h2 style="margin-top: 2em;">Dark Mode Results</h2>
907
	<div class="table-wrapper">
908
		<h2>Summary of Compatible Color Combinations (<?= $current_method === 'both' ? 'Both' : strtoupper($current_method) ?>)</h2>
909
		<table class="checkered">
910
			<thead>
911
				<tr>
912
					<th>Background</th>
913
					<?php if ($current_method === 'wcag'): ?>
914
						<th>AAA ≥ 7.0<br>Best</th>
915
						<th>AA Normal ≥ 4.5<br>Second Best</th>
916
						<th>AA Large ≥ 3.0<br>Third Best</th>
917
					<?php elseif ($current_method === 'apca'): ?>
918
						<th>Perfect ≥ 90</th>
919
						<th>Excellent ≥ 75</th>
920
						<th>Good ≥ 60</th>
921
						<th>Fair ≥ 45</th>
922
					<?php else: // both ?>
923
						<th>Perfect<br>WCAG&nbsp;≥10&nbsp;&<br>APCA&nbsp;≥90</th>
924
						<th>Excellent<br>WCAG&nbsp;≥7&nbsp;&<br>APCA&nbsp;≥75</th>
925
						<th>Good<br>WCAG&nbsp;≥4.5&nbsp;&<br>APCA&nbsp;≥60</th>
926
						<th>Fair<br>WCAG&nbsp;≥3&nbsp;&<br>APCA&nbsp;≥45</th>
927
					<?php endif; ?>
928
				</tr>
929
			</thead>
930
			<tbody>
931
				<?php foreach ($bg_colors as $bg_color => $bg): 
932
					// Calculate all combinations for this background color
933
					$combinations = [];
934
					foreach ($fg_colors as $fg_color => $fg) {
935
						
936
						if ($current_method === 'wcag') {
937
							// Calculate luminance with alpha blending
938
							$fg_lum = getLuminance($fg['rgb'], $fg['alpha'], $bg['rgb']);
939
							$bg_lum = $bg['luminance'];
940
							$contrast = getContrastRatio($bg_lum, $fg_lum);
941
							$level = getWCAGLevel($contrast);
942
							
943
							if ($level !== 'Fail') {
944
								$combinations[] = [
945
									'color' => $fg_color,
946
									'contrast' => $contrast,
947
									'level' => $level
948
								];
949
							}
950
						} elseif ($current_method === 'apca') {
951
							// APCA calculations
952
							$contrast = getAPCAContrast($bg['rgb'], $fg['rgb'], $bg['alpha'], $fg['alpha']);
953
							$level = getAPCALevel($contrast);
954
							
955
							if (!str_contains($level, 'Fail')) {
956
								$combinations[] = [
957
									'color' => $fg_color,
958
									'contrast' => $contrast,
959
									'level' => $level
960
								];
961
							}
962
						} else { // both
963
							// Calculate both WCAG and APCA
964
							$fg_lum = getLuminance($fg['rgb'], $fg['alpha'], $bg['rgb']);
965
							$bg_lum = $bg['luminance'];
966
							$wcag_contrast = getContrastRatio($bg_lum, $fg_lum);
967
							$wcag_level = getWCAGLevel($wcag_contrast);
968
							
969
							$apca_contrast = getAPCAContrast($bg['rgb'], $fg['rgb'], $bg['alpha'], $fg['alpha']);
970
							$apca_level = getAPCALevel($apca_contrast);
971
							
972
							$combined_level = getCombinedLevel($wcag_contrast, $apca_contrast);
973
							
974
							if ($combined_level !== 'Fail') {
975
								$combinations[] = [
976
									'color' => $fg_color,
977
									'wcag_contrast' => $wcag_contrast,
978
									'apca_contrast' => $apca_contrast,
979
									'level' => $combined_level
980
								];
981
							}
982
						}
983
					}
984
985
					if ($current_method === 'wcag') {
986
						// Group by WCAG level
987
						$wcag_groups = [
988
							'AAA' => [],
989
							'AA' => [],
990
							'AA Large' => []
991
						];
992
						
993
						foreach ($combinations as $combo) {
994
							$wcag_groups[$combo['level']][] = $combo;
995
						}
996
						
997
						// Sort each group by contrast ratio
998
						foreach ($wcag_groups as &$group) {
999
							usort($group, function($a, $b) {
1000
								return $b['contrast'] <=> $a['contrast'];
1001
							});
1002
						}
1003
1004
						$has_valid_combinations = !empty($wcag_groups['AAA']) || 
1005
												!empty($wcag_groups['AA']) || 
1006
												!empty($wcag_groups['AA Large']);
1007
					} elseif ($current_method === 'apca') {
1008
						// Group by APCA level
1009
						$apca_groups = [
1010
							'Perfect' => [],
1011
							'Excellent' => [],
1012
							'Good' => [],
1013
							'Fair' => []
1014
						];
1015
						
1016
						foreach ($combinations as $combo) {
1017
							$level_key = explode(' - ', $combo['level'])[0];
1018
							if (isset($apca_groups[$level_key])) {
1019
								$apca_groups[$level_key][] = $combo;
1020
							}
1021
						}
1022
						
1023
						// Sort each group by absolute contrast value
1024
						foreach ($apca_groups as &$group) {
1025
							usort($group, function($a, $b) {
1026
								return abs($b['contrast']) <=> abs($a['contrast']);
1027
							});
1028
						}
1029
1030
						$has_valid_combinations = !empty($apca_groups['Perfect']) || 
1031
												!empty($apca_groups['Excellent']) || 
1032
												!empty($apca_groups['Good']) || 
1033
												!empty($apca_groups['Fair']);
1034
					} else { // both
1035
						// Group by combined level
1036
						$combined_groups = [
1037
							'Perfect' => [],
1038
							'Excellent' => [],
1039
							'Good' => [],
1040
							'Fair' => []
1041
						];
1042
						
1043
						foreach ($combinations as $combo) {
1044
							if (isset($combined_groups[$combo['level']])) {
1045
								$combined_groups[$combo['level']][] = $combo;
1046
							}
1047
						}
1048
						
1049
						// Sort each group by WCAG contrast ratio
1050
						foreach ($combined_groups as &$group) {
1051
							usort($group, function($a, $b) {
1052
								return $b['wcag_contrast'] <=> $a['wcag_contrast'];
1053
							});
1054
						}
1055
1056
						$has_valid_combinations = !empty($combined_groups['Perfect']) || 
1057
												!empty($combined_groups['Excellent']) || 
1058
												!empty($combined_groups['Good']) || 
1059
												!empty($combined_groups['Fair']);
1060
					}
1061
				?>
1062
				<tr style="background-color: <?= htmlspecialchars(getCssColor($bg_color)) ?>;">
1063
					<td>
1064
						<?php 
1065
						// Use alpha-aware luminance for text color calculation
1066
						$bg_text_lum = getLuminance($bg['rgb'], $bg['alpha']); ?>
1067
						<span style="color: <?= $bg_text_lum > 0.5 ? '#000000' : '#FFFFFF' ?>"><?= htmlspecialchars(getCleanColorName($bg_color)) ?></span>
1068
					</td>
1069
					<?php if ($has_valid_combinations): ?>
1070
						<?php if ($current_method === 'wcag'): ?>
1071
							<?php foreach (['AAA', 'AA', 'AA Large'] as $level): ?>
1072
								<td><?php foreach ($wcag_groups[$level] as $combo): ?>
1073
									<div style="color: <?= htmlspecialchars(getCssColor($combo['color'])) ?>;"><?= htmlspecialchars($combo['color']) ?>&nbsp;(Ratio:&nbsp;<?= number_format($combo['contrast'], 2) ?>)</div>
1074
								<?php endforeach; ?></td>
1075
							<?php endforeach; ?>
1076
						<?php elseif ($current_method === 'apca'): ?>
1077
							<?php foreach (['Perfect', 'Excellent', 'Good', 'Fair'] as $level): ?>
1078
								<td><?php foreach ($apca_groups[$level] as $combo): ?>
1079
									<div style="color: <?= htmlspecialchars(getCssColor($combo['color'])) ?>;"><?= htmlspecialchars($combo['color']) ?>&nbsp;(Lc:&nbsp;<?= number_format($combo['contrast'], 1) ?>)</div>
1080
								<?php endforeach; ?></td>
1081
							<?php endforeach; ?>
1082
						<?php else: // both ?>
1083
							<?php foreach (['Perfect', 'Excellent', 'Good', 'Fair'] as $level): ?>
1084
								<td><?php foreach ($combined_groups[$level] as $combo): ?>
1085
									<div style="color: <?= htmlspecialchars(getCssColor($combo['color'])) ?>;"><?= htmlspecialchars($combo['color']) ?>&nbsp;(<?= number_format($combo['wcag_contrast'], 2) ?>)&nbsp;(Lc&nbsp;<?= number_format($combo['apca_contrast'], 1) ?>)</div>
1086
								<?php endforeach; ?></td>
1087
							<?php endforeach; ?>
1088
						<?php endif; ?>
1089
					<?php else: ?>
1090
						<td colspan="<?= $current_method === 'wcag' ? '3' : '4' ?>" style="text-align: center; color: <?= $bg_text_lum > 0.5 ? '#000000' : '#FFFFFF' ?>">
1091
							<?php if ($current_method === 'wcag'): ?>
1092
								No valid color combinations found (all contrast ratios below 3.0)
1093
							<?php elseif ($current_method === 'apca'): ?>
1094
								No valid color combinations found (all APCA values below 45)
1095
							<?php else: ?>
1096
								No valid color combinations found (failed both WCAG and APCA thresholds)
1097
							<?php endif; ?>
1098
						</td>
1099
					<?php endif; ?>
1100
				</tr>
1101
				<?php endforeach; ?>
1102
			</tbody>
1103
		</table>
1104
	</div>
1105
1106
	<div class="table-wrapper">
1107
	<h2>Complete Contrast Grid - Dark Mode (<?= $current_method === 'both' ? 'Both' : strtoupper($current_method) ?>)</h2>
1108
	<table class="checkered">
1109
	<tr>
1110
		<th>BG \ FG</th>
1111
	<?php foreach ($fg_colors as $color => $data): ?>
1112
		<th><?= htmlspecialchars(getCleanColorName($color)) ?></th>
1113
	<?php endforeach; ?>
1114
	</tr>
1115
	<?php foreach ($bg_colors as $bg_color => $bg_data): ?>
1116
	<tr>
1117
		<th><?= htmlspecialchars(getCleanColorName($bg_color)) ?></th>
1118
		<?php foreach ($fg_colors as $fg_color => $fg_data): 
1119
			if ($current_method === 'wcag') {
1120
				// Calculate luminance with alpha blending
1121
				$fg_lum = getLuminance($fg_data['rgb'], $fg_data['alpha'], $bg_data['rgb']);
1122
				$bg_lum = $bg_data['luminance'];
1123
				$contrast = getContrastRatio($bg_lum, $fg_lum);
1124
				$level = getWCAGLevel($contrast);
1125
				$display_value = number_format($contrast, 2);
1126
			} elseif ($current_method === 'apca') {
1127
				// APCA calculations
1128
				$contrast = getAPCAContrast($bg_data['rgb'], $fg_data['rgb'], $bg_data['alpha'], $fg_data['alpha']);
1129
				$level = getAPCALevel($contrast);
1130
				$display_value = number_format($contrast, 1);
1131
			} else { // both
1132
				// Calculate both WCAG and APCA
1133
				$fg_lum = getLuminance($fg_data['rgb'], $fg_data['alpha'], $bg_data['rgb']);
1134
				$bg_lum = $bg_data['luminance'];
1135
				$wcag_contrast = getContrastRatio($bg_lum, $fg_lum);
1136
				$wcag_level = getWCAGLevel($wcag_contrast);
1137
				
1138
				$apca_contrast = getAPCAContrast($bg_data['rgb'], $fg_data['rgb'], $bg_data['alpha'], $fg_data['alpha']);
1139
				$apca_level = getAPCALevel($apca_contrast);
1140
				
1141
				$combined_level = getCombinedLevel($wcag_contrast, $apca_contrast);
1142
			}
1143
		?>
1144
			<td style="background-color: <?= htmlspecialchars(getCssColor($bg_color)) ?>;">
1145
				<!-- color value hover / alt text? -->
1146
				<div class="sample-text" style="color: <?= htmlspecialchars(getCssColor($fg_color)) ?>;">Sample
1147
				<div style="font-size: 0.8em;">
1148
					<?php if ($current_method === 'wcag'): ?>
1149
						<?= $display_value ?><br><?= $level ?>
1150
					<?php elseif ($current_method === 'apca'): ?>
1151
						Lc&nbsp;<?= $display_value ?><br><?= explode(' - ', $level)[0] ?>
1152
					<?php else: // both ?>
1153
						(<?= number_format($wcag_contrast, 2) ?>) (Lc&nbsp;<?= number_format($apca_contrast, 1) ?>)<br><?= $combined_level ?>
1154
					<?php endif; ?>
1155
				</div>
1156
				</div>
1157
			</td>
1158
	<?php endforeach; ?>
1159
	</tr>
1160
	<?php endforeach; ?>
1161
	</table>
1162
	</div>
1163
<?php endif; ?>
1164
	<div class="timer"><?php 
1165
	$end = hrtime(true);
1166
	$nanoseconds = $end - $start;
1167
	// convert time to readable values
1168
		// Create an array of time units and their nanosecond conversions
1169
	$units = [
1170
		'second' => 1e9,
1171
		'millisecond' => 1e6,
1172
		'microsecond' => 1e3,
1173
		'nanosecond' => 1
1174
	];
1175
1176
	// Find the most appropriate unit
1177
	$value = $nanoseconds;
1178
	$unit = 'nanosecond';
1179
1180
	foreach ($units as $name => $divisor) {
1181
		if ($nanoseconds >= $divisor) {
1182
			$value = $nanoseconds / $divisor;
1183
			$unit = $name;
1184
			break;
1185
		}
1186
	}
1187
1188
	// Format the output with appropriate pluralization
1189
	$plural = $value != 1 ? 's' : '';
1190
	$seconds = $nanoseconds / 1e9; // Always show seconds as well if using a different unit
1191
1192
	if ($unit !== 'second') {
1193
		echo sprintf("This code took %.6f seconds to execute, which is %.0f %s%s.", 
1194
			$seconds, $value, $unit, $plural);
1195
	} else {
1196
		echo sprintf("This code took %.6f seconds to execute.", $seconds);
1197
	}
1198
	?></div>
1199
	<div class="footer"><?php echo getCopyrightYears(2024); ?>
1200
	</div>
1201
	<script src="https://contrast.jefftml.com/tablewidth.js"></script>
1202
	<script>
1203
	function toggleAdvancedMode() {
1204
		var advancedMode = document.getElementById('advanced_mode').checked;
1205
		var normalModeInput = document.getElementById('normal_mode_input');
1206
		var advancedModeInputs = document.getElementById('advanced_mode_inputs');
1207
		var colorsTextarea = document.getElementById('colors');
1208
		
1209
		if (advancedMode) {
1210
			normalModeInput.style.display = 'none';
1211
			advancedModeInputs.style.display = 'block';
1212
			if (colorsTextarea) {
1213
				colorsTextarea.removeAttribute('required');
1214
			}
1215
		} else {
1216
			normalModeInput.style.display = 'block';
1217
			advancedModeInputs.style.display = 'none';
1218
			if (colorsTextarea) {
1219
				colorsTextarea.setAttribute('required', 'required');
1220
			}
1221
		}
1222
	}
1223
	
1224
	// Initialize on page load
1225
	if (document.readyState === 'loading') {
1226
		document.addEventListener('DOMContentLoaded', toggleAdvancedMode);
1227
	} else {
1228
		toggleAdvancedMode();
1229
	}
1230
	
1231
	// Scroll to results if they exist (after form submission)
1232
	function scrollToResults() {
1233
		var resultsAnchor = document.getElementById('results');
1234
		if (resultsAnchor) {
1235
			// Check if there are any results or error messages
1236
			var hasResults = document.querySelector('.error-message, .warning-message, .table-wrapper') !== null;
1237
			
1238
			if (hasResults) {
1239
				// Small delay to ensure page is fully rendered
1240
				setTimeout(function() {
1241
					resultsAnchor.scrollIntoView({ behavior: 'smooth', block: 'start' });
1242
				}, 100);
1243
			}
1244
		}
1245
	}
1246
	
1247
	// Run scroll check on page load
1248
	if (document.readyState === 'loading') {
1249
		document.addEventListener('DOMContentLoaded', scrollToResults);
1250
	} else {
1251
		scrollToResults();
1252
	}
1253
	</script>
1254
	</div>
1255
	</main>
1256
</body>
1257
</html>