-
- HTTP
-
- - - - - -
- - - - - -
- -
- - - -
-
-
-
-
-
+
+
-
+
+
+
+
+
+ permission.site
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
- HTTPS
-
-Dark Mode
+- - - - - -
- - - - - -
- -
Async Clipboard API
- - - -- - - -
-
Notes:
-Augmented Reality (AR) | -Implemented behind the experimental flag chrome://flags/#enable-experimental-web-platform-features . |
-
Encrypted Media (EME) | -May succeed without permission depending on the implementation. - Attempts to use known key systems. (See the source for the list of supported key systems.) - - |
-
Async Clipboard | -
- These buttons test the new Async Clipboard API.
- - Note that these tests are different from the "Copy" button on this page, which uses the old - (synchronous) execCommand method to write to the clipboard.
- - This feature is implemented behind the experimental flag chrome://flags/#enable-experimental-web-platform-features .
- - To enable the Content Settings UX for setting clipboard permission, you'll also need to enable - chrome://flags/#clipboard-content-setting
- - Note: Only available for secure connections (https). - |
-
WebAuthn Attestation | -- After pressing the button, you may need to touch your security key before you can see an attestation prompt. - | -
Device Orientation/Motion | -- May succeed without permission request depending on the implementation. - | -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + +
+ + + + + + +
+ +
+ + + +
+
+
+
+
-+ + + + + +
+ + + + + + +
+ +
Async Clipboard API
+ + + ++ + + +
+
Notes:
+Augmented Reality (AR) | +
+ Implemented behind the experimental flag
+ chrome://flags/#enable-experimental-web-platform-features .
+ |
+
Encrypted Media (EME) | +
+ May succeed without permission depending on the implementation. + Attempts to use known key systems. (See the source for the list of + supported key systems.) + + |
+
Async Clipboard | +
+ These buttons test the new
+ Async Clipboard API.
+ + Note that these tests are different from the "Copy" button on this + page, which uses the old (synchronous) + execCommand method to write to the clipboard.
+ + This feature is implemented behind the experimental flag + chrome://flags/#enable-experimental-web-platform-features .
+ + To enable the Content Settings UX for setting clipboard permission, + you'll also need to enable + chrome://flags/#clipboard-content-setting
+ + Note: Only available for secure connections (https). + |
+
WebAuthn Attestation | ++ After pressing the button, you may need to touch your security key + before you can see an attestation prompt. + | +
Device Orientation/Motion | ++ May succeed without permission request depending on the + implementation. + | +
-
+
-
-
-
+
+
diff --git a/index.js b/index.js
index 674692e..2e6de86 100644
--- a/index.js
+++ b/index.js
@@ -1,8 +1,47 @@
-
// - Information about clearing settings in Chrome (can't link to chrome:// URLs)
// - Indicate if permissions are already granted, if the relevant API allows it.
-window.addEventListener("load", function() {
+window.addEventListener("load", function () {
+ function setTheme(mode) {
+ if (mode === "dark") {
+ document.body.classList.add("dark-mode");
+ localStorage.setItem("theme", "dark");
+ document.getElementById("darkmodeInput").checked = true;
+ } else {
+ document.body.classList.remove("dark-mode");
+ localStorage.setItem("theme", "light");
+ document.getElementById("darkmodeInput").checked = false;
+ }
+ }
+
+ // Check for a stored theme in localStorage
+ let theme = localStorage.getItem("theme");
+
+ // If there's no stored theme, detect the OS preference
+ if (!theme) {
+ const osPrefersDark = window.matchMedia(
+ "(prefers-color-scheme: dark)"
+ ).matches;
+ theme = osPrefersDark ? "dark" : "light";
+ }
+
+ // Apply the theme on page load based on the stored or detected theme
+ setTheme(theme);
+
+ // Listen for changes in the OS color scheme and update if no manual override
+ window
+ .matchMedia("(prefers-color-scheme: dark)")
+ .addEventListener("change", (e) => {
+ if (!localStorage.getItem("theme")) {
+ setTheme(e.matches ? "dark" : "light");
+ }
+ });
+
+ // Add event listener for the dark mode toggle
+ document.getElementById("darkmodeInput").addEventListener("change", () => {
+ const isDarkMode = document.body.classList.contains("dark-mode");
+ setTheme(isDarkMode ? "light" : "dark");
+ });
var toggle = document.querySelector("#toggle");
toggle.classList.add("instant");
@@ -13,14 +52,14 @@ window.addEventListener("load", function() {
toggle.classList.add("http");
toggle.protocol = "https:";
}
- setTimeout(function() {
+ setTimeout(function () {
toggle.classList.remove("instant");
}, 10);
function displayOutcome(type, outcome) {
- return function() {
+ return function () {
var argList = [outcome, type].concat([].slice.call(arguments));
- switch(outcome) {
+ switch (outcome) {
case "success":
console.info.apply(console, argList);
break;
@@ -30,13 +69,15 @@ window.addEventListener("load", function() {
default:
console.log.apply(console, argList);
}
- document.getElementById(type).classList.remove('success', 'error', 'default');
+ document
+ .getElementById(type)
+ .classList.remove("success", "error", "default");
document.getElementById(type).classList.add(outcome);
};
- };
+ }
function displayOutcomeForNotifications(outcome) {
- switch(outcome) {
+ switch (outcome) {
case "granted":
console.info(outcome, "notifications");
document.getElementById("notifications").classList.add("success");
@@ -51,66 +92,65 @@ window.addEventListener("load", function() {
default:
throw "Unknown notification API response.";
}
- };
+ }
function triggerDownload() {
// Based on http://stackoverflow.com/a/27280611
- var a = document.createElement('a');
+ var a = document.createElement("a");
a.download = "test-image.png";
- a.href = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAABC0lEQVQYlTXPPUsCYQDA8b/e04tdQR5ZBpE3NAR6S0SDVDZKDQ2BY9TUy1foE0TQ1Edo6hOEkyUG0QuBRtQgl0hnenVdnZD5eLbU7xv8Avy5X16KhrQBg47EtpziXO6qBhAEeNEm0qr7VdBcLxt2mlnNbhVu0NMAgdj1wvjOoX2xdSt0L7MGgx2GGid8yLrJvJMUkbKfOF8N68bUIqcz2wQR7GUcYvJIr1dFQijvkh89xGV6BPPMwvMF/nQXJMgWiM+KLPX2tc0HNa/HUxDv2owpx7xV+023Hiwpdt7yhmcjj9/NdrIhn8LrPVmotctWVd01Nt27wH9T3YhHU5O+sT/6SuVZKa4cNGoAv/ZMas7pC/KaAAAAAElFTkSuQmCC";
+ a.href =
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAABC0lEQVQYlTXPPUsCYQDA8b/e04tdQR5ZBpE3NAR6S0SDVDZKDQ2BY9TUy1foE0TQ1Edo6hOEkyUG0QuBRtQgl0hnenVdnZD5eLbU7xv8Avy5X16KhrQBg47EtpziXO6qBhAEeNEm0qr7VdBcLxt2mlnNbhVu0NMAgdj1wvjOoX2xdSt0L7MGgx2GGid8yLrJvJMUkbKfOF8N68bUIqcz2wQR7GUcYvJIr1dFQijvkh89xGV6BPPMwvMF/nQXJMgWiM+KLPX2tc0HNa/HUxDv2owpx7xV+023Hiwpdt7yhmcjj9/NdrIhn8LrPVmotctWVd01Nt27wH9T3YhHU5O+sT/6SuVZKa4cNGoAv/ZMas7pC/KaAAAAAElFTkSuQmCC";
a.click();
}
function isFullscreen() {
- return document.fullscreenElement ||
- document.webkitFullscreenElement ||
- document.mozFullScreenElement ||
- document.msFullscreenElement;
+ return (
+ document.fullscreenElement ||
+ document.webkitFullscreenElement ||
+ document.mozFullScreenElement ||
+ document.msFullscreenElement
+ );
}
function isPointerLocked() {
- return document.pointerLockElement ||
- document.webkitPointerLockElement ||
- document.mozPointerLockElement ||
- document.msPointerLockElement;
+ return (
+ document.pointerLockElement ||
+ document.webkitPointerLockElement ||
+ document.mozPointerLockElement ||
+ document.msPointerLockElement
+ );
}
- navigator.getUserMedia = (
+ navigator.getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
- navigator.msGetUserMedia
- );
- navigator.requestMIDIAccess = (
+ navigator.msGetUserMedia;
+ navigator.requestMIDIAccess =
navigator.requestMIDIAccess ||
navigator.webkitRequestMIDIAccess ||
navigator.mozRequestMIDIAccess ||
- navigator.msRequestMIDIAccess
- );
- document.documentElement.requestFullscreen = (
+ navigator.msRequestMIDIAccess;
+ document.documentElement.requestFullscreen =
document.documentElement.requestFullscreen ||
document.documentElement.webkitRequestFullscreen ||
document.documentElement.mozRequestFullscreen ||
- document.documentElement.msRequestFullscreen
- );
- document.exitFullscreen = (
+ document.documentElement.msRequestFullscreen;
+ document.exitFullscreen =
document.exitFullscreen ||
document.webkitExitFullscreen ||
document.mozCancelFullScreen ||
- document.msExitFullscreen
- );
- document.body.requestPointerLock = (
+ document.msExitFullscreen;
+ document.body.requestPointerLock =
document.body.requestPointerLock ||
document.body.webkitRequestPointerLock ||
document.body.mozRequestPointerLock ||
- document.body.msRequestPointerLock
- );
- document.exitPointerLock = (
+ document.body.msRequestPointerLock;
+ document.exitPointerLock =
document.exitPointerLock ||
document.webkitExitPointerLock ||
document.mozExitPointerLock ||
- document.msExitPointerLock
- );
+ document.msExitPointerLock;
document.addEventListener("fullscreenchange", (e) => {
displayOutcome("fullscreen", isFullscreen() ? "success" : "default")(e);
@@ -126,137 +166,148 @@ window.addEventListener("load", function() {
});
var register = {
- "notifications": function () {
- Notification.requestPermission(
- displayOutcomeForNotifications
- );
+ notifications: function () {
+ Notification.requestPermission(displayOutcomeForNotifications);
},
- "location": function() {
+ location: function () {
navigator.geolocation.getCurrentPosition(
displayOutcome("location", "success"),
displayOutcome("location", "error")
);
},
- "camera": function() {
- navigator.mediaDevices ?
- navigator.mediaDevices.getUserMedia(
- {video: true}).then(
+ camera: function () {
+ navigator.mediaDevices
+ ? navigator.mediaDevices
+ .getUserMedia({ video: true })
+ .then(
+ displayOutcome("camera", "success"),
+ displayOutcome("camera", "error")
+ )
+ : navigator.getUserMedia(
+ { video: true },
displayOutcome("camera", "success"),
displayOutcome("camera", "error")
- ) :
- navigator.getUserMedia(
- {video: true},
- displayOutcome("camera", "success"),
- displayOutcome("camera", "error")
- );
+ );
},
- "microphone": function() {
- navigator.mediaDevices ?
- navigator.mediaDevices.getUserMedia(
- {audio: true}).then(
+ microphone: function () {
+ navigator.mediaDevices
+ ? navigator.mediaDevices
+ .getUserMedia({ audio: true })
+ .then(
+ displayOutcome("microphone", "success"),
+ displayOutcome("microphone", "error")
+ )
+ : navigator.getUserMedia(
+ { audio: true },
displayOutcome("microphone", "success"),
displayOutcome("microphone", "error")
- ) :
- navigator.getUserMedia(
- {audio: true},
- displayOutcome("microphone", "success"),
- displayOutcome("microphone", "error")
- );
+ );
},
- "camera+microphone": function() {
- navigator.mediaDevices ?
- navigator.mediaDevices.getUserMedia(
- {audio: true, video: true}).then(
+ "camera+microphone": function () {
+ navigator.mediaDevices
+ ? navigator.mediaDevices
+ .getUserMedia({ audio: true, video: true })
+ .then(
+ displayOutcome("camera+microphone", "success"),
+ displayOutcome("camera+microphone", "error")
+ )
+ : navigator.getUserMedia(
+ { audio: true, video: true },
displayOutcome("camera+microphone", "success"),
displayOutcome("camera+microphone", "error")
- ) :
- navigator.getUserMedia(
- {audio: true, video: true},
- displayOutcome("camera+microphone", "success"),
- displayOutcome("camera+microphone", "error")
- );
+ );
},
- "pan-tilt-zoom": function() {
- navigator.mediaDevices ?
- navigator.mediaDevices.getUserMedia(
- {video: {pan: true, tilt: true, zoom: true}}).then(
+ "pan-tilt-zoom": function () {
+ navigator.mediaDevices
+ ? navigator.mediaDevices
+ .getUserMedia({ video: { pan: true, tilt: true, zoom: true } })
+ .then(
+ displayOutcome("pan-tilt-zoom", "success"),
+ displayOutcome("pan-tilt-zoom", "error")
+ )
+ : navigator.getUserMedia(
+ { video: { pan: true, tilt: true, zoom: true } },
displayOutcome("pan-tilt-zoom", "success"),
displayOutcome("pan-tilt-zoom", "error")
- ) :
- navigator.getUserMedia(
- {video: {pan: true, tilt: true, zoom: true}},
- displayOutcome("pan-tilt-zoom", "success"),
- displayOutcome("pan-tilt-zoom", "error")
- );
+ );
},
- "pan-tilt-zoom+microphone": function() {
- navigator.mediaDevices ?
- navigator.mediaDevices.getUserMedia(
- {video: {pan: true, tilt: true, zoom: true}, audio: true}).then(
+ "pan-tilt-zoom+microphone": function () {
+ navigator.mediaDevices
+ ? navigator.mediaDevices
+ .getUserMedia({
+ video: { pan: true, tilt: true, zoom: true },
+ audio: true,
+ })
+ .then(
+ displayOutcome("pan-tilt-zoom+microphone", "success"),
+ displayOutcome("pan-tilt-zoom+microphone", "error")
+ )
+ : navigator.getUserMedia(
+ { video: { pan: true, tilt: true, zoom: true }, audio: true },
displayOutcome("pan-tilt-zoom+microphone", "success"),
displayOutcome("pan-tilt-zoom+microphone", "error")
- ) :
- navigator.getUserMedia(
- {video: {pan: true, tilt: true, zoom: true}, audio: true},
- displayOutcome("pan-tilt-zoom+microphone", "success"),
- displayOutcome("pan-tilt-zoom+microphone", "error")
- );
- },
- "screenshare": function() {
- navigator.mediaDevices.getDisplayMedia().then(
- displayOutcome("screenshare", "success"),
- displayOutcome("screenshare", "error")
- );
+ );
},
- "midi": function() {
- navigator.requestMIDIAccess({
- sysex: false
- }).then(
- displayOutcome("midi", "success"),
- displayOutcome("midi", "error")
- );
+ screenshare: function () {
+ navigator.mediaDevices
+ .getDisplayMedia()
+ .then(
+ displayOutcome("screenshare", "success"),
+ displayOutcome("screenshare", "error")
+ );
},
- "midi+sysex": function() {
- navigator.requestMIDIAccess({
- sysex: true
- }).then(
- displayOutcome("midi+sysex", "success"),
- displayOutcome("midi+sysex", "error")
- );
+ midi: function () {
+ navigator
+ .requestMIDIAccess({
+ sysex: false,
+ })
+ .then(
+ displayOutcome("midi", "success"),
+ displayOutcome("midi", "error")
+ );
},
- "bluetooth": function() {
- navigator.bluetooth.requestDevice({
- // filters: [...] <- Prefer filters to save energy & show relevant devices.
- // acceptAllDevices here ensures dialog can populate, we don't care with what.
- acceptAllDevices:true
- })
- .then(device => device.gatt.connect())
- .then(
- displayOutcome("bluetooth", "success"),
- displayOutcome("bluetooth", "error")
- );
+ "midi+sysex": function () {
+ navigator
+ .requestMIDIAccess({
+ sysex: true,
+ })
+ .then(
+ displayOutcome("midi+sysex", "success"),
+ displayOutcome("midi+sysex", "error")
+ );
},
- "usb": function() {
- navigator.usb.requestDevice({filters: [{}]}).then(
- displayOutcome("usb", "success"),
- displayOutcome("usb", "error")
- );
+ bluetooth: function () {
+ navigator.bluetooth
+ .requestDevice({
+ // filters: [...] <- Prefer filters to save energy & show relevant devices.
+ // acceptAllDevices here ensures dialog can populate, we don't care with what.
+ acceptAllDevices: true,
+ })
+ .then((device) => device.gatt.connect())
+ .then(
+ displayOutcome("bluetooth", "success"),
+ displayOutcome("bluetooth", "error")
+ );
},
- "serial": function() {
- navigator.serial.requestPort({filters: []}).then(
- displayOutcome("serial", "success"),
- displayOutcome("serial", "error")
- );
+ usb: function () {
+ navigator.usb
+ .requestDevice({ filters: [{}] })
+ .then(displayOutcome("usb", "success"), displayOutcome("usb", "error"));
+ },
+ serial: function () {
+ navigator.serial
+ .requestPort({ filters: [] })
+ .then(
+ displayOutcome("serial", "success"),
+ displayOutcome("serial", "error")
+ );
},
- "hid": function() {
- navigator.hid.requestDevice({filters: []}).then(
- devices => {
- displayOutcome("hid", devices.length > 0 ? "success" : "error")();
- },
- displayOutcome("hid", "error")
- );
+ hid: function () {
+ navigator.hid.requestDevice({ filters: [] }).then((devices) => {
+ displayOutcome("hid", devices.length > 0 ? "success" : "error")();
+ }, displayOutcome("hid", "error"));
},
- "eme": function() {
+ eme: function () {
// https://w3c.github.io/encrypted-media/#requestMediaKeySystemAccess
// Tries multiple configuration per key system. The configurations are in
// descending order of privileges such that a supported permission-requiring
@@ -264,16 +315,16 @@ window.addEventListener("load", function() {
// require permissions.
var knownKeySystems = [
- "com.example.somesystem", // Ensure no real system is the first tried.
+ "com.example.somesystem", // Ensure no real system is the first tried.
"com.widevine.alpha",
"com.microsoft.playready",
"com.adobe.primetime",
"com.apple.fps.2_0",
"com.apple.fps",
"com.apple.fps.1_0",
- "com.example.somesystem" // Ensure no real system is the last tried.
+ "com.example.somesystem", // Ensure no real system is the last tried.
];
- var tryKeySystem = function(keySystem) {
+ var tryKeySystem = function (keySystem) {
// http://w3c.github.io/encrypted-media/#idl-def-mediakeysystemconfiguration
// One of videoCapabilities or audioCapabilities must be present. Pick
// a set that all browsers should support at least one of.
@@ -281,43 +332,48 @@ window.addEventListener("load", function() {
{ contentType: 'audio/mp4; codecs="mp4a.40.2"' },
{ contentType: 'audio/webm; codecs="opus"' },
];
- navigator.requestMediaKeySystemAccess(
- keySystem,
- [
- { distinctiveIdentifier: "required",
+ navigator
+ .requestMediaKeySystemAccess(keySystem, [
+ {
+ distinctiveIdentifier: "required",
persistentState: "required",
audioCapabilities: capabilities,
- label: "'distinctiveIdentifier' and 'persistentState' required"
+ label: "'distinctiveIdentifier' and 'persistentState' required",
},
- { distinctiveIdentifier: "required",
+ {
+ distinctiveIdentifier: "required",
audioCapabilities: capabilities,
- label: "'distinctiveIdentifier' required"
+ label: "'distinctiveIdentifier' required",
},
- { persistentState: "required",
+ {
+ persistentState: "required",
audioCapabilities: capabilities,
- label: "'persistentState' required"
+ label: "'persistentState' required",
},
- { audioCapabilities: capabilities,
- label: "empty"
+ { audioCapabilities: capabilities, label: "empty" },
+ { label: "no capabilities" },
+ ])
+ .then(
+ function (mediaKeySystemAccess) {
+ displayOutcome("eme", "success")(
+ "Key System: " + keySystem,
+ "Configuration: " +
+ mediaKeySystemAccess.getConfiguration().label,
+ mediaKeySystemAccess
+ );
},
- { label: "no capabilities" }
- ]
- ).then(
- function (mediaKeySystemAccess) {
- displayOutcome("eme", "success")(
- "Key System: " + keySystem,
- "Configuration: " + mediaKeySystemAccess.getConfiguration().label,
- mediaKeySystemAccess);
- },
- function (error) {
- if (knownKeySystems.length > 0)
- return tryKeySystem(knownKeySystems.shift());
-
- displayOutcome("eme", "error")(
- error,
- error.name == "NotSupportedError" ? "No known key systems supported or permitted." : "");
- }
- );
+ function (error) {
+ if (knownKeySystems.length > 0)
+ return tryKeySystem(knownKeySystems.shift());
+
+ displayOutcome("eme", "error")(
+ error,
+ error.name == "NotSupportedError"
+ ? "No known key systems supported or permitted."
+ : ""
+ );
+ }
+ );
};
tryKeySystem(knownKeySystems.shift());
},
@@ -335,89 +391,102 @@ window.addEventListener("load", function() {
try {
const status = await IdleDetector.requestPermission();
if (status != "granted") {
- displayOutcome("idle-detection", "error")(`Permission status: ${status}`);
+ displayOutcome(
+ "idle-detection",
+ "error"
+ )(`Permission status: ${status}`);
return;
}
controller = new AbortController();
const detector = new IdleDetector();
- detector.addEventListener('change', () => {
- console.log(`Idle change: ${detector.userState}, ${detector.screenState}.`);
+ detector.addEventListener("change", () => {
+ console.log(
+ `Idle change: ${detector.userState}, ${detector.screenState}.`
+ );
});
- await detector.start({signal: controller.signal});
+ await detector.start({ signal: controller.signal });
displayOutcome("idle-detection", "success")();
} catch (e) {
controller = null;
displayOutcome("idle-detection", "error")(e);
}
};
- }()),
- "copy": (function() {
+ })(),
+ copy: (function () {
var interceptCopy = false;
- document.addEventListener("copy", function(e){
+ document.addEventListener("copy", function (e) {
if (interceptCopy) {
// From http://www.w3.org/TR/clipboard-apis/#h4_the-copy-action
- e.clipboardData.setData("text/plain",
+ e.clipboardData.setData(
+ "text/plain",
"This text was copied from the permission.site clipboard example."
);
- e.clipboardData.setData("text/html",
+ e.clipboardData.setData(
+ "text/html",
"This text was copied from the " +
- "" +
- "permission.site clipboard example."
+ "" +
+ "permission.site clipboard example."
);
e.preventDefault();
}
});
- return function() {
+ return function () {
interceptCopy = true;
document.execCommand("copy");
interceptCopy = false;
};
- }()),
- "popup": function() {
+ })(),
+ popup: function () {
var w = window.open(
location.href,
"Popup",
"resizable,scrollbars,status"
- )
+ );
displayOutcome("popup", w ? "success" : "error")(w);
},
- "popup-delayed": function() {
- setTimeout(function() {
+ "popup-delayed": function () {
+ setTimeout(function () {
var w = window.open(
location.href,
"Popup",
"resizable,scrollbars,status"
- )
+ );
displayOutcome("popup-delayed", w ? "success" : "error")(w);
}, 5000);
},
- "fullscreen": function() {
+ fullscreen: function () {
try {
if (!isFullscreen()) {
- document.documentElement.requestFullscreen().then(
- displayOutcome("fullscreen", "success")("enter"),
- displayOutcome("fullscreen", "error")
- );
+ document.documentElement
+ .requestFullscreen()
+ .then(
+ displayOutcome("fullscreen", "success")("enter"),
+ displayOutcome("fullscreen", "error")
+ );
} else {
- document.exitFullscreen().then(
- displayOutcome("fullscreen", "default")("exit"),
- displayOutcome("fullscreen", "error")
- );
+ document
+ .exitFullscreen()
+ .then(
+ displayOutcome("fullscreen", "default")("exit"),
+ displayOutcome("fullscreen", "error")
+ );
}
} catch (e) {
displayOutcome("fullscreen", "error")(e);
}
},
- "pointerlock": function() {
+ pointerlock: function () {
try {
if (!window.pointerLocked) {
- document.body.requestPointerLock().then(
- displayOutcome("pointerlock", "success")("locked"),
- displayOutcome("pointerlock", "error")
- );
+ document.body
+ .requestPointerLock()
+ .then(
+ displayOutcome("pointerlock", "success")("locked"),
+ displayOutcome("pointerlock", "error")
+ );
} else {
document.exitPointerLock();
displayOutcome("pointerlock", "default")("unlocked");
@@ -426,15 +495,20 @@ window.addEventListener("load", function() {
displayOutcome("pointerlock", "error")(e);
}
},
- "keyboardlock": function() {
+ keyboardlock: function () {
try {
if (!window.keyboardLockRequested) {
window.keyboardLockRequested = true;
// Note: As of 2023-12-14, Chrome resolves the promise immediately and holds the lock in a pending state when the document is not fullscreen.
- navigator.keyboard.lock().then(
- displayOutcome("keyboardlock", "success")(isFullscreen() ? "locked" : "will lock in fullscreen"),
- displayOutcome("keyboardlock", "error")
- );
+ navigator.keyboard
+ .lock()
+ .then(
+ displayOutcome(
+ "keyboardlock",
+ "success"
+ )(isFullscreen() ? "locked" : "will lock in fullscreen"),
+ displayOutcome("keyboardlock", "error")
+ );
} else {
window.keyboardLockRequested = false;
navigator.keyboard.unlock();
@@ -444,219 +518,302 @@ window.addEventListener("load", function() {
displayOutcome("keyboardlock", "error")(e);
}
},
- "download": function() {
+ download: function () {
// Two downloads at the same time trigger a permission prompt in Chrome.
triggerDownload();
triggerDownload();
},
- "keygen": function() {
+ keygen: function () {
var keygen = document.createElement("keygen");
document.getElementById("keygen-container").appendChild(keygen);
},
- "persistent-storage": function() {
+ "persistent-storage": function () {
// https://storage.spec.whatwg.org
- navigator.storage.persist().then(
- function(persisted) {
- displayOutcome("persistent-storage", persisted ? "success" : "default")(persisted);
- },
- displayOutcome("persistent-storage", "error")
- )
+ navigator.storage.persist().then(function (persisted) {
+ displayOutcome(
+ "persistent-storage",
+ persisted ? "success" : "default"
+ )(persisted);
+ }, displayOutcome("persistent-storage", "error"));
},
-
- "protocol-handler": function() {
+
+ "protocol-handler": function () {
// https://www.w3.org/TR/html5/webappapis.html#navigatorcontentutils
- var url = window.location + '%s';
+ var url = window.location + "%s";
try {
- navigator.registerProtocolHandler('web+permissionsite', url, 'title');
- } catch(e) {
+ navigator.registerProtocolHandler("web+permissionsite", url, "title");
+ } catch (e) {
displayOutcome("protocol-handler", "error")(e);
}
},
- "read-text": function() {
+ "read-text": function () {
var cb = navigator.clipboard;
if (cb) {
- cb.readText().then(function(data) {
- displayOutcome("read-text", "success")("Successfully read data from clipboard: '" + data + "'");
- }, function() {
- displayOutcome("read-text", "error")("Failed to read from clipboard");
- });
+ cb.readText().then(
+ function (data) {
+ displayOutcome(
+ "read-text",
+ "success"
+ )("Successfully read data from clipboard: '" + data + "'");
+ },
+ function () {
+ displayOutcome(
+ "read-text",
+ "error"
+ )("Failed to read from clipboard");
+ }
+ );
} else {
- displayOutcome("read-text", "error")("navigator.clipboard not available");
+ displayOutcome(
+ "read-text",
+ "error"
+ )("navigator.clipboard not available");
}
},
- "write-text": function() {
+ "write-text": function () {
var cb = navigator.clipboard;
if (cb) {
- navigator.clipboard.writeText("new clipboard data").then(function() {
- displayOutcome("write-text", "success")("Successfully wrote data to clipboard");
- }, function() {
- displayOutcome("write-text", "error")("Failed to write to clipboard");
- });
+ navigator.clipboard.writeText("new clipboard data").then(
+ function () {
+ displayOutcome(
+ "write-text",
+ "success"
+ )("Successfully wrote data to clipboard");
+ },
+ function () {
+ displayOutcome(
+ "write-text",
+ "error"
+ )("Failed to write to clipboard");
+ }
+ );
} else {
- displayOutcome("write-text", "error")("navigator.clipboard not available");
+ displayOutcome(
+ "write-text",
+ "error"
+ )("navigator.clipboard not available");
}
},
- "read-text-delayed": function() {
+ "read-text-delayed": function () {
var cb = navigator.clipboard;
if (cb) {
- setTimeout(function() {
- navigator.clipboard.readText().then(function(data) {
- displayOutcome("read-text-delayed", "success")("Successfully read data from clipboard: '" + data + "'");
- }, function() {
- displayOutcome("read-text-delayed", "error")("Failed to read from clipboard");
- });
+ setTimeout(function () {
+ navigator.clipboard.readText().then(
+ function (data) {
+ displayOutcome(
+ "read-text-delayed",
+ "success"
+ )("Successfully read data from clipboard: '" + data + "'");
+ },
+ function () {
+ displayOutcome(
+ "read-text-delayed",
+ "error"
+ )("Failed to read from clipboard");
+ }
+ );
}, 5000);
} else {
- displayOutcome("read-text-delayed", "error")("navigator.clipboard not available");
+ displayOutcome(
+ "read-text-delayed",
+ "error"
+ )("navigator.clipboard not available");
}
},
- "write-text-delayed": function() {
+ "write-text-delayed": function () {
var cb = navigator.clipboard;
if (cb) {
- setTimeout(function() {
- navigator.clipboard.writeText("new (delayed) clipboard data").then(function() {
- displayOutcome("write-text-delayed", "success")("Successfully wrote data to clipboard");
- }, function() {
- displayOutcome("write-text-delayed", "error")("Failed to write to clipboard");
- });
+ setTimeout(function () {
+ navigator.clipboard.writeText("new (delayed) clipboard data").then(
+ function () {
+ displayOutcome(
+ "write-text-delayed",
+ "success"
+ )("Successfully wrote data to clipboard");
+ },
+ function () {
+ displayOutcome(
+ "write-text-delayed",
+ "error"
+ )("Failed to write to clipboard");
+ }
+ );
}, 5000);
} else {
- displayOutcome("write-text-delayed", "error")("navigator.clipboard not available");
+ displayOutcome(
+ "write-text-delayed",
+ "error"
+ )("navigator.clipboard not available");
}
},
- "webauthn-attestation": function() {
+ "webauthn-attestation": function () {
// From https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API
// This code is public domain, per https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses
// sample arguments for registration
var createCredentialDefaultArgs = {
- publicKey: {
- // Relying Party (a.k.a. - Service):
- rp: {
- name: "Acme"
- },
-
- // User:
- user: {
- id: new Uint8Array(16),
- name: "john.p.smith@example.com",
- displayName: "John P. Smith"
- },
-
- pubKeyCredParams: [{
- type: "public-key",
- alg: -7
- }],
-
- attestation: "direct",
-
- timeout: 60000,
-
- challenge: new Uint8Array([ // must be a cryptographically random number sent from a server
- 0x8C, 0x0A, 0x26, 0xFF, 0x22, 0x91, 0xC1, 0xE9, 0xB9, 0x4E, 0x2E, 0x17, 0x1A, 0x98, 0x6A, 0x73,
- 0x71, 0x9D, 0x43, 0x48, 0xD5, 0xA7, 0x6A, 0x15, 0x7E, 0x38, 0x94, 0x52, 0x77, 0x97, 0x0F, 0xEF
- ]).buffer
- }
+ publicKey: {
+ // Relying Party (a.k.a. - Service):
+ rp: {
+ name: "Acme",
+ },
+
+ // User:
+ user: {
+ id: new Uint8Array(16),
+ name: "john.p.smith@example.com",
+ displayName: "John P. Smith",
+ },
+
+ pubKeyCredParams: [
+ {
+ type: "public-key",
+ alg: -7,
+ },
+ ],
+
+ attestation: "direct",
+
+ timeout: 60000,
+
+ challenge: new Uint8Array([
+ // must be a cryptographically random number sent from a server
+ 0x8c, 0x0a, 0x26, 0xff, 0x22, 0x91, 0xc1, 0xe9, 0xb9, 0x4e, 0x2e,
+ 0x17, 0x1a, 0x98, 0x6a, 0x73, 0x71, 0x9d, 0x43, 0x48, 0xd5, 0xa7,
+ 0x6a, 0x15, 0x7e, 0x38, 0x94, 0x52, 0x77, 0x97, 0x0f, 0xef,
+ ]).buffer,
+ },
};
// sample arguments for login
var getCredentialDefaultArgs = {
- publicKey: {
- timeout: 60000,
- // allowCredentials: [newCredential] // see below
- challenge: new Uint8Array([ // must be a cryptographically random number sent from a server
- 0x79, 0x50, 0x68, 0x71, 0xDA, 0xEE, 0xEE, 0xB9, 0x94, 0xC3, 0xC2, 0x15, 0x67, 0x65, 0x26, 0x22,
- 0xE3, 0xF3, 0xAB, 0x3B, 0x78, 0x2E, 0xD5, 0x6F, 0x81, 0x26, 0xE2, 0xA6, 0x01, 0x7D, 0x74, 0x50
- ]).buffer
- },
+ publicKey: {
+ timeout: 60000,
+ // allowCredentials: [newCredential] // see below
+ challenge: new Uint8Array([
+ // must be a cryptographically random number sent from a server
+ 0x79, 0x50, 0x68, 0x71, 0xda, 0xee, 0xee, 0xb9, 0x94, 0xc3, 0xc2,
+ 0x15, 0x67, 0x65, 0x26, 0x22, 0xe3, 0xf3, 0xab, 0x3b, 0x78, 0x2e,
+ 0xd5, 0x6f, 0x81, 0x26, 0xe2, 0xa6, 0x01, 0x7d, 0x74, 0x50,
+ ]).buffer,
+ },
};
// register / create a new credential
- navigator.credentials.create(createCredentialDefaultArgs)
- .then((cred) => {
- console.log("NEW CREDENTIAL", cred);
-
- // normally the credential IDs available for an account would come from a server
- // but we can just copy them from above...
- var idList = [{
- id: cred.rawId,
- transports: ["usb", "nfc", "ble"],
- type: "public-key"
- }];
- getCredentialDefaultArgs.publicKey.allowCredentials = idList;
- return navigator.credentials.get(getCredentialDefaultArgs);
- })
- .then((assertion) => {
- displayOutcome("webauthn-attestation", "success")(assertion);
- })
- .catch((err) => {
- displayOutcome("webauthn-attestation", "error")(err);
- });
- },
- "nfc": function() {
- if ('NDEFReader' in window) {
- const reader = new NDEFReader();
- reader.scan()
- .then(() => {
- displayOutcome("nfc", "success")("Successfully started NFC scan");
+ navigator.credentials
+ .create(createCredentialDefaultArgs)
+ .then((cred) => {
+ console.log("NEW CREDENTIAL", cred);
+
+ // normally the credential IDs available for an account would come from a server
+ // but we can just copy them from above...
+ var idList = [
+ {
+ id: cred.rawId,
+ transports: ["usb", "nfc", "ble"],
+ type: "public-key",
+ },
+ ];
+ getCredentialDefaultArgs.publicKey.allowCredentials = idList;
+ return navigator.credentials.get(getCredentialDefaultArgs);
+ })
+ .then((assertion) => {
+ displayOutcome("webauthn-attestation", "success")(assertion);
})
.catch((err) => {
- displayOutcome("nfc", "error")(err);
+ displayOutcome("webauthn-attestation", "error")(err);
});
+ },
+ nfc: function () {
+ if ("NDEFReader" in window) {
+ const reader = new NDEFReader();
+ reader
+ .scan()
+ .then(() => {
+ displayOutcome("nfc", "success")("Successfully started NFC scan");
+ })
+ .catch((err) => {
+ displayOutcome("nfc", "error")(err);
+ });
} else {
displayOutcome("nfc", "error")("NDEFReader is not available");
}
},
- "vr": function() {
- if ('xr' in navigator) {
- navigator.xr.requestSession('immersive-vr')
- .then(() => {
- displayOutcome("vr", "success")("Successfully entered VR");
- })
- .catch((err) => {
- displayOutcome("vr", "error")(err);
- });
+ vr: function () {
+ if ("xr" in navigator) {
+ navigator.xr
+ .requestSession("immersive-vr")
+ .then(() => {
+ displayOutcome("vr", "success")("Successfully entered VR");
+ })
+ .catch((err) => {
+ displayOutcome("vr", "error")(err);
+ });
} else {
displayOutcome("vr", "error")("navigator.xr is not available");
}
},
- "ar": function() {
- if ('xr' in navigator) {
- navigator.xr.requestSession('immersive-ar')
- .then(() => {
- displayOutcome("ar", "success")("Successfully entered AR");
- })
- .catch((err) => {
- displayOutcome("ar", "error")(err);
- });
+ ar: function () {
+ if ("xr" in navigator) {
+ navigator.xr
+ .requestSession("immersive-ar")
+ .then(() => {
+ displayOutcome("ar", "success")("Successfully entered AR");
+ })
+ .catch((err) => {
+ displayOutcome("ar", "error")(err);
+ });
} else {
displayOutcome("ar", "error")("navigator.xr is not available");
}
},
- "orientation": function() {
+ orientation: function () {
if ("ondeviceorientation" in window) {
- const handleDeviceOrientation = () => window.addEventListener("deviceorientation", (event) => {
- if (event.alpha === null && event.beta === null && event.gamma === null) {
- displayOutcome("orientation", "error")("Device has no the required sensors");
- } else {
- displayOutcome("orientation", "success")("Device has the required sensors");
- }
- }, { once: true });
+ const handleDeviceOrientation = () =>
+ window.addEventListener(
+ "deviceorientation",
+ (event) => {
+ if (
+ event.alpha === null &&
+ event.beta === null &&
+ event.gamma === null
+ ) {
+ displayOutcome(
+ "orientation",
+ "error"
+ )("Device has no the required sensors");
+ } else {
+ displayOutcome(
+ "orientation",
+ "success"
+ )("Device has the required sensors");
+ }
+ },
+ { once: true }
+ );
- if (window.DeviceOrientationEvent && window.DeviceOrientationEvent.requestPermission) {
+ if (
+ window.DeviceOrientationEvent &&
+ window.DeviceOrientationEvent.requestPermission
+ ) {
window.DeviceOrientationEvent.requestPermission()
.then((permissionState) => {
- console.log(`Device Orientation permission state: ${permissionState}`);
+ console.log(
+ `Device Orientation permission state: ${permissionState}`
+ );
if (permissionState !== "granted") {
// If permission prompt is ignored or dismissed,
// the permission state value is `default`, and permission can be requested again.
// https://w3c.github.io/deviceorientation/#id=permission-model
- displayOutcome("orientation", "error")(`Device Orientation permission state: ${permissionState}`);
+ displayOutcome(
+ "orientation",
+ "error"
+ )(`Device Orientation permission state: ${permissionState}`);
} else {
handleDeviceOrientation();
}
@@ -669,30 +826,47 @@ window.addEventListener("load", function() {
handleDeviceOrientation();
}
} else {
- displayOutcome("orientation", "error")("Device Orientation is not supported");
+ displayOutcome(
+ "orientation",
+ "error"
+ )("Device Orientation is not supported");
}
},
- "motion": function() {
+ motion: function () {
if ("ondevicemotion" in window) {
- const handleDeviceMotion = () => window.addEventListener("devicemotion", (event) => {
- if (
- event.acceleration.x === null &&
- event.acceleration.y === null &&
- event.acceleration.z === null &&
- event.accelerationIncludingGravity.x === null &&
- event.accelerationIncludingGravity.y === null &&
- event.accelerationIncludingGravity.z === null &&
- event.rotationRate.alpha === null &&
- event.rotationRate.beta === null &&
- event.rotationRate.gamma === null
- ) {
- displayOutcome("motion", "error")("Device has no the required sensors");
- } else {
- displayOutcome("motion", "success")("Device has the required sensors");
- }
- }, { once: true });
+ const handleDeviceMotion = () =>
+ window.addEventListener(
+ "devicemotion",
+ (event) => {
+ if (
+ event.acceleration.x === null &&
+ event.acceleration.y === null &&
+ event.acceleration.z === null &&
+ event.accelerationIncludingGravity.x === null &&
+ event.accelerationIncludingGravity.y === null &&
+ event.accelerationIncludingGravity.z === null &&
+ event.rotationRate.alpha === null &&
+ event.rotationRate.beta === null &&
+ event.rotationRate.gamma === null
+ ) {
+ displayOutcome(
+ "motion",
+ "error"
+ )("Device has no the required sensors");
+ } else {
+ displayOutcome(
+ "motion",
+ "success"
+ )("Device has the required sensors");
+ }
+ },
+ { once: true }
+ );
- if (window.DeviceMotionEvent && window.DeviceMotionEvent.requestPermission) {
+ if (
+ window.DeviceMotionEvent &&
+ window.DeviceMotionEvent.requestPermission
+ ) {
window.DeviceMotionEvent.requestPermission()
.then((permissionState) => {
console.log(`Device Motion permission state: ${permissionState}`);
@@ -700,7 +874,10 @@ window.addEventListener("load", function() {
// If permission prompt is ignored or dismissed,
// the permission state value is `default`, and permission can be requested again.
// https://w3c.github.io/deviceorientation/#id=permission-model
- displayOutcome("motion", "error")(`Device Motion permission state: ${permissionState}`);
+ displayOutcome(
+ "motion",
+ "error"
+ )(`Device Motion permission state: ${permissionState}`);
} else {
handleDeviceMotion();
}
@@ -715,13 +892,10 @@ window.addEventListener("load", function() {
} else {
displayOutcome("motion", "error")("Device Motion is not supported");
}
- }
+ },
};
for (var type in register) {
- document.getElementById(type).addEventListener('click',
- register[type]
- );
+ document.getElementById(type).addEventListener("click", register[type]);
}
-
});
diff --git a/style.css b/style.css
index e3b64d7..89173a3 100644
--- a/style.css
+++ b/style.css
@@ -1,14 +1,16 @@
/* Light material design boilerplate. */
-html, body {
+html,
+body {
width: 100%;
margin: 0;
padding: 0;
- background: #EEEEEE;
+ background: #eeeeee;
}
/* Only apply min-height to non-print media */
@media not print {
- html, body {
+ html,
+ body {
min-height: 100%;
}
}
@@ -35,24 +37,36 @@ body {
}
button {
- background: #FFF; border: none;
+ background: #fff;
+ border: none;
font-size: 1.5em;
- width: 400px; padding: 0.5em; margin: 10px 10px;
- box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
+ width: 400px;
+ padding: 0.5em;
+ margin: 10px 10px;
+ box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
transition: all 150ms;
}
button:hover {
- background: #FFA;
+ background: #ffa;
cursor: pointer;
- box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
transform: translateY(-2px);
}
button.success {
- background: #AFA;
+ background: #afa;
}
button.error {
- background: #FAA;
+ background: #faa;
+}
+
+.darkMode {
+ display: flex;
+ width: 100%;
+ justify-content: left;
+ align-items: center;
+ gap: 0.5rem;
}
+
table {
margin: 1em auto;
border-collapse: collapse;
@@ -79,41 +93,38 @@ table td:first-child {
}
#toggle .http,
-#toggle .https
-{
+#toggle .https {
color: rgba(0, 0, 0, 0.5);
transition: color 0.25s ease-in-out;
}
#toggle.https .http,
-#toggle.http .https
-{
+#toggle.http .https {
color: rgba(0, 0, 0, 0.5);
}
#toggle.http:not(:hover) .http {
- color: #DB4437;
+ color: #db4437;
}
#toggle.https:not(:hover) .https {
- color: #1AC222;
+ color: #1ac222;
}
#toggle.http:hover .https {
text-decoration: underline;
- color: #1AC222;
+ color: #1ac222;
}
#toggle.https:hover .http {
text-decoration: underline;
- color: #DB4437;
+ color: #db4437;
}
a#toggle:hover {
color: black;
}
-
.switch {
display: inline-block;
width: 34px;
@@ -129,12 +140,10 @@ a#toggle:hover {
height: 20px;
margin: -3px;
border-radius: 10px;
- background-color: #F1F1F1;
- border:
- inset 0.5px rgba(255, 255, 255, 0.12),
+ background-color: #f1f1f1;
+ border: inset 0.5px rgba(255, 255, 255, 0.12),
inset 0.5px rgba(255, 255, 255, 0.12);
- box-shadow:
- 0px 0px 2px 0px rgba(0, 0, 0, 0.12),
+ box-shadow: 0px 0px 2px 0px rgba(0, 0, 0, 0.12),
0px 2px 2px 0px rgba(0, 0, 0, 0.24);
transform: translateX(10px);
@@ -144,56 +153,52 @@ a#toggle:hover {
#toggle.instant .switch,
#toggle.instant .switch .knob,
#toggle.instant .http,
-#toggle.instant .https
-{
+#toggle.instant .https {
transition: transform 0s;
}
-.http:not(:hover) .switch,
-.https:hover .switch
-{
+.http:not(:hover) .switch,
+.https:hover .switch {
background-color: rgba(219, 68, 55, 0.26);
}
-.http:not(:hover) .switch .knob,
-.https:hover .switch .knob
-{
+.http:not(:hover) .switch .knob,
+.https:hover .switch .knob {
background-color: rgb(219, 68, 55);
transform: translateX(0px);
}
-.http:hover .switch,
-.https:not(:hover) .switch
-{
+.http:hover .switch,
+.https:not(:hover) .switch {
background-color: rgba(29, 194, 34, 0.5);
}
-.http:hover .switch .knob,
-.https:not(:hover) .switch .knob
-{
+.http:hover .switch .knob,
+.https:not(:hover) .switch .knob {
background-color: rgb(29, 194, 34);
transform: translateX(20px);
}
-.jswarning
-{
+.jswarning {
color: rgb(219, 68, 55);
font-size: 2em;
}
-.permission-status, .access-status {
+.permission-status,
+.access-status {
font-size: small;
text-transform: uppercase;
- padding-top: .4rem;
+ padding-top: 0.4rem;
text-align: left;
}
-.permission-status > span, .access-status > span {
+.permission-status > span,
+.access-status > span {
font-weight: 800;
}
.demo-instructions {
- padding: .5rem 1rem;
+ padding: 0.5rem 1rem;
margin-top: 1rem;
text-align: left;
border: solid 1px;
@@ -204,10 +209,114 @@ a#toggle:hover {
permission {
background: #def0ff;
- border: solid #005763;
+ border: solid #005763;
border-radius: 5px;
width: 400px;
padding: 0.5em;
margin: 10px 10px;
display: block;
-}
\ No newline at end of file
+}
+
+/* Dark mode css */
+
+.switch1 {
+ position: relative;
+ display: inline-block;
+ width: 40px;
+ height: 24px;
+}
+
+/* Hide default HTML checkbox */
+.switch1 input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
+
+/* The slider */
+.slider1 {
+ position: absolute;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: #ccc;
+ -webkit-transition: 0.4s;
+ transition: 0.4s;
+}
+
+.slider1:before {
+ position: absolute;
+ content: "";
+ height: 16px;
+ width: 16px;
+ left: 4px;
+ bottom: 4px;
+ background-color: white;
+ -webkit-transition: 0.4s;
+ transition: 0.4s;
+}
+
+input:checked + .slider1 {
+ background-color: #2196f3;
+}
+
+input:focus + .slider1 {
+ box-shadow: 0 0 1px #2196f3;
+}
+
+input:checked + .slider1:before {
+ -webkit-transform: translateX(16px);
+ -ms-transform: translateX(16px);
+ transform: translateX(16px);
+}
+
+/* Rounded sliders */
+.slider1.round {
+ border-radius: 34px;
+}
+
+.slider1.round:before {
+ border-radius: 50%;
+}
+
+/* Dark Mode */
+
+/* Dark mode styles */
+body.dark-mode {
+ background: #333;
+ color: #eee;
+ transition: 0.5s all;
+}
+
+body.dark-mode button {
+ background: #444;
+ color: #eee;
+ transition: 0.5s all;
+}
+
+body.dark-mode .switch {
+ background-color: rgba(29, 194, 34, 0.5);
+ transition: 0.5s all;
+}
+
+body.dark-mode .content {
+ background-color: #444;
+ transition: 0.5s all;
+}
+
+body.dark-mode .slider1 {
+ background-color: #888;
+ transition: 0.5s all;
+}
+
+body.dark-mode .slider1:before {
+ background-color: #eee;
+ transition: 0.5s all;
+}
+
+#darkmodeInput:checked + .slider1 {
+ background-color: #2196f3;
+ transition: 0.5s all;
+}
+
-