Compare commits

..

No commits in common. "master" and "v0.3.0" have entirely different histories.

9 changed files with 118 additions and 234 deletions

View File

@ -2,8 +2,8 @@
<h1 align="center">Browser Extension for Tube Archivist</h1> <h1 align="center">Browser Extension for Tube Archivist</h1>
<div align="center"> <div align="center">
<a href="https://addons.mozilla.org/addon/tubearchivist-companion/" target="_blank"><img src="https://tiles.tilefy.me/t/tubearchivist-firefox.png" alt="tubearchivist-firefox" title="TA Companion Firefox users" height="50" width="190"/></a> <a href="https://www.tilefy.me" target="_blank"><img src="https://tiles.tilefy.me/t/tubearchivist-firefox.png" alt="tubearchivist-firefox" title="TA Companion Firefox users" height="50" width="190"/></a>
<a href="https://chrome.google.com/webstore/detail/tubearchivist-companion/jjnkmicfnfojkkgobdfeieblocadmcie" target="_blank"><img src="https://tiles.tilefy.me/t/tubearchivist-chrome.png" alt="tubearchivist-chrome" title="TA Companion Chrome users" height="50" width="190"/></a> <a href="https://www.tilefy.me" target="_blank"><img src="https://tiles.tilefy.me/t/tubearchivist-chrome.png" alt="tubearchivist-chrome" title="TA Companion Chrome users" height="50" width="190"/></a>
</div> </div>
## Core Functionality ## Core Functionality
@ -45,14 +45,12 @@ After a new release here on GitHub, you'll get updates automatically in your bro
## Setup ## Setup
- **URL**: This is where your Tube Archivist instance is located. Can be a host name or an IP address. Add the port if needed at the end, e.g. `:8000`. - **URL**: This is where your Tube Archivist instance is located. Can be a host name or an IP address. Add the port if needed at the end, e.g. `:8000`.
- **API key**: You can find your API key on the settings page (Settings -> Application -> Integrations section -> API token) of your Tube Archivist instance. - **API key**: You can find your API key on the settings page of your Tube Archivist instance.
A green checkmark will appear next to the *Save* button if your connection is working. A green checkmark will appear next to the *Save* button if your connection is working.
## Options ## Options
- **Continuous Cookie Sync**: Automatically and continuously update the cookie on change. - **Sync YouTube cookies**: Send your cookies to TubeArchivist to use for yt-dlp requests.
- **Copy Now**: Copy the cookie now to TA.
- **Show Cookie**: Show the cookie on click, for copy paste.
- **Autostart**: Autostart and prioritize videos send from this extension. - **Autostart**: Autostart and prioritize videos send from this extension.
## Test this extension ## Test this extension
@ -72,9 +70,6 @@ Symlink/copy the correct manifest file for your browser to the expected location
- Open the folder containing the *manifest.json* file. - Open the folder containing the *manifest.json* file.
- Click on *Service Worker* to open the dev tools at background.js. - Click on *Service Worker* to open the dev tools at background.js.
Note:
- If you are running your TA dev setup outside of the container, you need to point the URL to the backend and _not_ the frontend. E.g. localhost:8000 and not localhost:3000.
## Compatibility ## Compatibility
- Verify that you are running the [latest version](https://github.com/tubearchivist/tubearchivist/releases/latest) of Tube Archivist as the API is under development and will change. - Verify that you are running the [latest version](https://github.com/tubearchivist/tubearchivist/releases/latest) of Tube Archivist as the API is under development and will change.
- For testing this extension between releases, use the *unstable* builds of Tube Archivist, only for your testing environment. - For testing this extension between releases, use the *unstable* builds of Tube Archivist, only for your testing environment.

View File

@ -3,8 +3,8 @@
set -e set -e
if [[ $(basename "$(pwd)") != 'browser-extension' ]]; then if [[ $(basename "$(pwd)") != 'tubearchivist_browserextension' ]]; then
echo 'not in browser-extension folder' echo 'not in tubearchivist_browserextension folder'
exit 1 exit 1
fi fi

View File

@ -47,7 +47,7 @@ async function sendData(path, payload, method) {
let access = await getAccess(); let access = await getAccess();
const url = `${access.url}:${access.port}/${path}`; const url = `${access.url}:${access.port}/${path}`;
console.log(`${method}: ${url}`); console.log(`${method}: ${url}`);
if (!path.endsWith('cookie/')) console.log(`${method}: ${JSON.stringify(payload)}`); console.log(`${method}: ${JSON.stringify(payload)}`);
try { try {
const rawResponse = await fetch(url, { const rawResponse = await fetch(url, {
@ -77,7 +77,7 @@ async function getAccess() {
// check if cookie is valid // check if cookie is valid
async function getCookieState() { async function getCookieState() {
const path = 'api/appsettings/cookie/'; const path = 'api/cookie/';
let response = await sendGet(path); let response = await sendGet(path);
console.log('cookie state: ' + JSON.stringify(response)); console.log('cookie state: ' + JSON.stringify(response));
@ -138,22 +138,54 @@ async function subscribe(url, subscribed) {
async function videoExists(id) { async function videoExists(id) {
const path = `api/video/${id}/`; const path = `api/video/${id}/`;
let response = await sendGet(path); let response = await sendGet(path);
if (response?.error) return false; if (!response.data) return false;
let access = await getAccess(); let access = await getAccess();
return new URL(`video/${id}/`, `${access.url}:${access.port}/`).href; return new URL(`video/${id}/`, `${access.url}:${access.port}/`).href;
} }
async function getChannel(channelHandle) { async function getChannelCache() {
const path = `api/channel/search/?q=${channelHandle}`; let cache = await browserType.storage.local.get('cache');
try { if (cache.cache) return cache;
return await sendGet(path); return { cache: {} };
} catch { }
return false;
async function setChannel(channelHandler, channelId) {
let cache = await getChannelCache();
cache.cache[channelHandler] = { id: channelId, timestamp: Date.now() };
browserType.storage.local.set(cache);
}
async function getChannelId(channelHandle) {
let cache = await getChannelCache();
if (cache.cache[channelHandle]) {
return cache.cache[channelHandle]?.id;
} }
let channel = await searchChannel(channelHandle);
if (channel) setChannel(channelHandle, channel.channel_id);
return channel.channel_id;
}
async function searchChannel(channelHandle) {
const path = `api/channel/search/?q=${channelHandle}`;
let response = await sendGet(path);
return response.data;
}
async function getChannel(channelHandle) {
let channelId = await getChannelId(channelHandle);
if (!channelId) return;
const path = `api/channel/${channelId}/`;
let response = await sendGet(path);
return response.data;
} }
async function cookieStr(cookieLines) { async function cookieStr(cookieLines) {
const path = 'api/appsettings/cookie/'; const path = 'api/cookie/';
let payload = { let payload = {
cookie: cookieLines.join('\n'), cookie: cookieLines.join('\n'),
}; };
@ -163,12 +195,9 @@ async function cookieStr(cookieLines) {
} }
function buildCookieLine(cookie) { function buildCookieLine(cookie) {
// 2nd argument controls subdomains, and must match leading dot in domain
let includeSubdomains = cookie.domain.startsWith('.') ? 'TRUE' : 'FALSE';
return [ return [
cookie.domain, cookie.domain,
includeSubdomains, 'TRUE',
cookie.path, cookie.path,
cookie.httpOnly.toString().toUpperCase(), cookie.httpOnly.toString().toUpperCase(),
Math.trunc(cookie.expirationDate) || 0, Math.trunc(cookie.expirationDate) || 0,
@ -177,8 +206,10 @@ function buildCookieLine(cookie) {
].join('\t'); ].join('\t');
} }
async function getCookieLines() { async function sendCookies() {
console.log('function sendCookies');
const acceptableDomains = ['.youtube.com', 'youtube.com', 'www.youtube.com']; const acceptableDomains = ['.youtube.com', 'youtube.com', 'www.youtube.com'];
let cookieStores = await browserType.cookies.getAllCookieStores(); let cookieStores = await browserType.cookies.getAllCookieStores();
let cookieLines = [ let cookieLines = [
'# Netscape HTTP Cookie File', '# Netscape HTTP Cookie File',
@ -198,46 +229,12 @@ async function getCookieLines() {
} }
} }
} }
return cookieLines;
}
async function sendCookies() {
console.log('function sendCookies');
let cookieLines = await getCookieLines();
let response = cookieStr(cookieLines); let response = cookieStr(cookieLines);
return response; return response;
} }
let listenerEnabled = false;
let isThrottled = false;
async function handleContinuousCookie(checked) {
if (checked === true) {
browserType.cookies.onChanged.addListener(onCookieChange);
listenerEnabled = true;
console.log('Cookie listener enabled');
} else {
browserType.cookies.onChanged.removeListener(onCookieChange);
listenerEnabled = false;
console.log('Cookie listener disabled');
}
}
function onCookieChange(changeInfo) {
if (!isThrottled) {
isThrottled = true;
console.log('Cookie event detected:', changeInfo);
sendCookies();
setTimeout(() => {
isThrottled = false;
}, 10000);
}
}
/* /*
process and return message if needed process and return message if needed
the following messages are supported: the following messages are supported:
@ -245,8 +242,6 @@ type Message =
| { type: 'verify' } | { type: 'verify' }
| { type: 'cookieState' } | { type: 'cookieState' }
| { type: 'sendCookie' } | { type: 'sendCookie' }
| { type: 'getCookieLines' }
| { type: 'continuousSync', checked: boolean }
| { type: 'download', url: string } | { type: 'download', url: string }
| { type: 'subscribe', url: string } | { type: 'subscribe', url: string }
| { type: 'unsubscribe', url: string } | { type: 'unsubscribe', url: string }
@ -270,12 +265,6 @@ function handleMessage(request, sender, sendResponse) {
case 'sendCookie': { case 'sendCookie': {
return await sendCookies(); return await sendCookies();
} }
case 'getCookieLines': {
return await getCookieLines();
}
case 'continuousSync': {
return await handleContinuousCookie(request.checked);
}
case 'download': { case 'download': {
return await download(request.url); return await download(request.url);
} }
@ -283,8 +272,8 @@ function handleMessage(request, sender, sendResponse) {
return await subscribe(request.url, true); return await subscribe(request.url, true);
} }
case 'unsubscribe': { case 'unsubscribe': {
let channel = await getChannel(request.url); let channelId = await getChannelId(request.url);
return await subscribe(channel.channel_id, false); return await subscribe(channelId, false);
} }
case 'videoExists': { case 'videoExists': {
return await videoExists(request.videoId); return await videoExists(request.videoId);
@ -301,7 +290,7 @@ function handleMessage(request, sender, sendResponse) {
})() })()
.then(value => sendResponse({ success: true, value })) .then(value => sendResponse({ success: true, value }))
.catch(e => { .catch(e => {
console.log(e); console.error(e);
let message = e?.message ?? e; let message = e?.message ?? e;
if (message === 'Failed to fetch') { if (message === 'Failed to fetch') {
// chrome's error message for failed `fetch` is not very user-friendly // chrome's error message for failed `fetch` is not very user-friendly
@ -313,9 +302,3 @@ function handleMessage(request, sender, sendResponse) {
} }
browserType.runtime.onMessage.addListener(handleMessage); browserType.runtime.onMessage.addListener(handleMessage);
browserType.runtime.onStartup.addListener(() => {
browserType.storage.local.get('continuousSync', data => {
handleContinuousCookie(data?.continuousSync?.checked || false);
});
});

View File

@ -14,7 +14,7 @@
<a href="#" id="ta-url" target="_blank"> <a href="#" id="ta-url" target="_blank">
<img src="/images/logo.png" alt="ta-logo"> <img src="/images/logo.png" alt="ta-logo">
</a> </a>
<span>v0.4.1</span> <span>v0.3.0</span>
</div> </div>
<hr> <hr>
<form class="login-form"> <form class="login-form">
@ -28,21 +28,12 @@
</div> </div>
<div id="error-out"></div> <div id="error-out"></div>
<hr> <hr>
<p>Cookies:</p> <p>Options:</p>
<div class="options"> <div class="options">
<div> <div>
<p>TA cookies: <span id="sendCookiesStatus" /></p> <input type="checkbox" id="sendCookies" name="sendCookies">
<span>Sync YouTube cookies</span><span id="sendCookiesStatus"></span>
</div> </div>
<div>
<input type="checkbox" id="continuous-sync" name="continuous-sync">
<span>Continuous Cookie Sync</span>
</div>
<button id="sendCookies">Copy Now</button>
<button id="showCookies">Show Cookie</button><br>
<textarea id="cookieLinesResponse" readonly></textarea>
</div>
<p>Download:</p>
<div class="options">
<div> <div>
<input type="checkbox" id="autostart" name="autostart"> <input type="checkbox" id="autostart" name="autostart">
<span>Autostart Downloads</span> <span>Autostart Downloads</span>
@ -55,7 +46,7 @@
<img src="/images/social/reddit.svg" alt="reddit-icon"></a> <img src="/images/social/reddit.svg" alt="reddit-icon"></a>
<a href="https://www.tubearchivist.com/discord" target="_blank"> <a href="https://www.tubearchivist.com/discord" target="_blank">
<img src="/images/social/discord.svg" alt="discord-icon"></a> <img src="/images/social/discord.svg" alt="discord-icon"></a>
<a href="https://github.com/tubearchivist/browser-extension/" target="_blank"> <a href="https://github.com/tubearchivist/tubearchivist" target="_blank">
<img src="/images/social/github.svg" alt="github-icon"> <img src="/images/social/github.svg" alt="github-icon">
</a> </a>
</div> </div>

View File

@ -2,7 +2,7 @@
"manifest_version": 3, "manifest_version": 3,
"name": "TubeArchivist Companion", "name": "TubeArchivist Companion",
"description": "Interact with your selfhosted TA server.", "description": "Interact with your selfhosted TA server.",
"version": "0.4.1", "version": "0.3.0",
"icons": { "icons": {
"48": "/images/icon.png", "48": "/images/icon.png",
"128": "/images/icon128.png" "128": "/images/icon128.png"

View File

@ -2,7 +2,7 @@
"manifest_version": 2, "manifest_version": 2,
"name": "TubeArchivist Companion", "name": "TubeArchivist Companion",
"description": "Interact with your selfhosted TA server.", "description": "Interact with your selfhosted TA server.",
"version": "0.4.1", "version": "0.3.0",
"icons": { "icons": {
"128": "/images/icon128.png" "128": "/images/icon128.png"
}, },

View File

@ -78,16 +78,6 @@ document.getElementById('sendCookies').addEventListener('click', function () {
sendCookie(); sendCookie();
}); });
// show cookies
document.getElementById('showCookies').addEventListener('click', function () {
showCookies();
});
// continuous sync
document.getElementById('continuous-sync').addEventListener('click', function () {
toggleContinuousSync();
});
// autostart // autostart
document.getElementById('autostart').addEventListener('click', function () { document.getElementById('autostart').addEventListener('click', function () {
toggleAutostart(); toggleAutostart();
@ -113,8 +103,8 @@ function sendCookie() {
function handleResponse(message) { function handleResponse(message) {
console.log('handle cookie response: ' + JSON.stringify(message)); console.log('handle cookie response: ' + JSON.stringify(message));
let validattionMessage = `enabled, last verified ${message.validated_str}`; let cookie_validated = message.cookie_validated;
document.getElementById('sendCookiesStatus').innerText = validattionMessage; document.getElementById('sendCookiesStatus').innerText = 'validated: ' + cookie_validated;
} }
function handleError(error) { function handleError(error) {
@ -122,44 +112,20 @@ function sendCookie() {
setError(error); setError(error);
} }
let sending = sendMessage({ type: 'sendCookie' }); let checked = document.getElementById('sendCookies').checked;
sending.then(handleResponse, handleError);
}
function showCookies() {
console.log('popup show cookies');
const textArea = document.getElementById('cookieLinesResponse');
function handleResponse(message) {
textArea.value = message.join('\n');
textArea.style.display = 'initial';
}
function handleError(error) {
console.log(`Error: ${error}`);
}
if (textArea.value) {
textArea.value = '';
textArea.style.display = 'none';
document.getElementById('showCookies').textContent = 'Show Cookie';
} else {
let sending = sendMessage({ type: 'getCookieLines' });
sending.then(handleResponse, handleError);
document.getElementById('showCookies').textContent = 'Hide Cookie';
}
}
function toggleContinuousSync() {
const checked = document.getElementById('continuous-sync').checked;
let toStore = { let toStore = {
continuousSync: { sendCookies: {
checked: checked, checked: checked,
}, },
}; };
browserType.storage.local.set(toStore, function () { browserType.storage.local.set(toStore, function () {
console.log('stored option: ' + JSON.stringify(toStore)); console.log('stored option: ' + JSON.stringify(toStore));
}); });
sendMessage({ type: 'continuousSync', checked }); if (checked === false) {
return;
}
let sending = sendMessage({ type: 'sendCookie' });
sending.then(handleResponse, handleError);
} }
function toggleAutostart() { function toggleAutostart() {
@ -175,7 +141,7 @@ function toggleAutostart() {
} }
// send ping message to TA backend // send ping message to TA backend
async function pingBackend() { function pingBackend() {
clearError(); clearError();
clearTempLocalStorage(); clearTempLocalStorage();
function handleResponse() { function handleResponse() {
@ -204,14 +170,9 @@ function setCookieState() {
clearError(); clearError();
function handleResponse(message) { function handleResponse(message) {
console.log(message); console.log(message);
if (!message.cookie_enabled) { document.getElementById('sendCookies').checked = message.cookie_enabled;
document.getElementById('sendCookiesStatus').innerText = 'disabled';
} else {
let validattionMessage = 'enabled';
if (message.validated_str) { if (message.validated_str) {
validattionMessage += `, last verified ${message.validated_str}`; document.getElementById('sendCookiesStatus').innerText = message.validated_str;
}
document.getElementById('sendCookiesStatus').innerText = validattionMessage;
} }
} }
@ -240,7 +201,7 @@ function setStatusIcon(connected) {
// fill in form // fill in form
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
async function onGot(item) { function onGot(item) {
if (!item.access) { if (!item.access) {
console.log('no access details found'); console.log('no access details found');
if (item.popupFullUrl != null && fullUrlInput.value === '') { if (item.popupFullUrl != null && fullUrlInput.value === '') {
@ -261,19 +222,18 @@ document.addEventListener('DOMContentLoaded', async () => {
document.getElementById('api-key').value = item.access.apiKey; document.getElementById('api-key').value = item.access.apiKey;
pingBackend(); pingBackend();
addUrl(item.access); addUrl(item.access);
setCookieState();
} }
async function setContinuousCookiesOptions(result) { function setCookiesOptions(result) {
if (!result.continuousSync || result.continuousSync.checked === false) { if (!result.sendCookies || result.sendCookies.checked === false) {
console.log('continuous cookie sync not set'); console.log('sync cookies not set');
return; return;
} }
console.log('set options: ' + JSON.stringify(result)); console.log('set options: ' + JSON.stringify(result));
document.getElementById('continuous-sync').checked = true; setCookieState();
} }
async function setAutostartOption(result) { function setAutostartOption(result) {
console.log(result); console.log(result);
if (!result.autostart || result.autostart.checked === false) { if (!result.autostart || result.autostart.checked === false) {
console.log('autostart not set'); console.log('autostart not set');
@ -287,8 +247,8 @@ document.addEventListener('DOMContentLoaded', async () => {
onGot(result); onGot(result);
}); });
browserType.storage.local.get('continuousSync', function (result) { browserType.storage.local.get('sendCookies', function (result) {
setContinuousCookiesOptions(result); setCookiesOptions(result);
}); });
browserType.storage.local.get('autostart', function (result) { browserType.storage.local.get('autostart', function (result) {

View File

@ -106,13 +106,11 @@ function getBrowser() {
} }
function getChannelContainers() { function getChannelContainers() {
const elements = document.querySelectorAll( const elements = document.querySelectorAll('.yt-flexible-actions-view-model-wiz, #owner');
'.page-header-view-model-wiz__page-header-flexible-actions, #owner'
);
const channelContainerNodes = []; const channelContainerNodes = [];
elements.forEach(element => { elements.forEach(element => {
if (isElementVisible(element) && window.location.pathname !== '/playlist') { if (isElementVisible(element)) {
channelContainerNodes.push(element); channelContainerNodes.push(element);
} }
}); });
@ -196,38 +194,22 @@ function buildChannelButton(channelContainer) {
} }
function getChannelHandle(channelContainer) { function getChannelHandle(channelContainer) {
function findeHandleString(container) { let channelHandle;
let result = null; const videoOwnerRenderer = channelContainer.querySelector('.ytd-video-owner-renderer');
function recursiveTraversal(element) { if (!videoOwnerRenderer) {
for (let child of element.children) { const channelHandleContainer = document.querySelector(
if (child.tagName === 'A' && child.hasAttribute('href')) { '.yt-content-metadata-view-model-wiz__metadata-text'
const href = child.getAttribute('href'); );
const match = href.match(/\/@[^/]+/); // Match the path starting with "@" channelHandle = channelHandleContainer ? channelHandleContainer.innerText : null;
if (match) { } else {
// handle is in channel link const href = videoOwnerRenderer.href;
result = match[0].substring(1); if (href) {
return; const urlObj = new URL(href);
channelHandle = urlObj.pathname.split('/')[1];
} }
} }
if (child.children.length === 0 && child.textContent.trim().startsWith('@')) {
// handle is in channel description text
result = child.textContent.trim();
return;
}
recursiveTraversal(child);
if (result) return;
}
}
recursiveTraversal(container);
return result;
}
let channelHandle = findeHandleString(channelContainer.parentElement);
return channelHandle; return channelHandle;
} }
@ -265,7 +247,6 @@ function buildChannelSubButton(channelHandle) {
} else { } else {
console.log('Unknown state'); console.log('Unknown state');
} }
e.stopPropagation();
}); });
Object.assign(channelSubButton.style, { Object.assign(channelSubButton.style, {
padding: '5px', padding: '5px',
@ -327,7 +308,6 @@ function buildChannelDownloadButton() {
e.preventDefault(); e.preventDefault();
console.log(`download: ${currentLocation}`); console.log(`download: ${currentLocation}`);
sendDownload(channelDownloadButton); sendDownload(channelDownloadButton);
e.stopPropagation();
}); });
Object.assign(channelDownloadButton.style, { Object.assign(channelDownloadButton.style, {
filter: 'invert()', filter: 'invert()',
@ -409,24 +389,6 @@ function buildVideoButton(titleContainer) {
} }
function getNearestLink(element) { function getNearestLink(element) {
// Check siblings
let sibling = element;
while (sibling) {
sibling = sibling.previousElementSibling;
if (sibling && sibling.tagName === 'A' && sibling.getAttribute('href') !== '#') {
return sibling.getAttribute('href');
}
}
sibling = element;
while (sibling) {
sibling = sibling.nextElementSibling;
if (sibling && sibling.tagName === 'A' && sibling.getAttribute('href') !== '#') {
return sibling.getAttribute('href');
}
}
// Check parent elements
for (let i = 0; i < 5 && element && element !== document; i++) { for (let i = 0; i < 5 && element && element !== document; i++) {
if (element.tagName === 'A' && element.getAttribute('href') !== '#') { if (element.tagName === 'A' && element.getAttribute('href') !== '#') {
return element.getAttribute('href'); return element.getAttribute('href');
@ -491,15 +453,13 @@ function checkVideoExists(taButton) {
console.error(e); console.error(e);
} }
let videoId = taButton.dataset.id; let aElem = taButton?.parentElement?.querySelector('a');
if (!videoId) { let videoId = getVideoId(aElem);;
videoId = getVideoId(taButton); if (aElem) {
if (videoId) {
taButton.setAttribute('data-id', videoId); taButton.setAttribute('data-id', videoId);
taButton.setAttribute('data-type', 'video'); taButton.setAttribute('data-type', 'video');
taButton.title = `TA download video: ${taButton.parentElement.innerText} [${videoId}]`; taButton.title = `TA download video: ${taButton.parentElement.innerText} [${videoId}]`;
} }
}
let message = { type: 'videoExists', videoId }; let message = { type: 'videoExists', videoId };
let sending = sendMessage(message); let sending = sendMessage(message);

View File

@ -18,20 +18,6 @@ hr {
background-color: white; background-color: white;
margin: 10px 0; margin: 10px 0;
} }
button {
margin: 10px;
border-radius: 0;
padding: 5px 13px;
border: none;
cursor: pointer;
background-color: #259485;
color: #ffffff;
}
button:hover {
background-color: #97d4c8;
transform: scale(1.05);
color: #00202f;
}
#download { #download {
text-align: center text-align: center
} }
@ -42,7 +28,7 @@ button:hover {
position: relative; position: relative;
} }
.logo img { .logo img {
width: 400px; width: 100%;
} }
.logo span { .logo span {
position: absolute; position: absolute;
@ -62,6 +48,20 @@ button:hover {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.submit button {
margin: 10px;
border-radius: 0;
padding: 5px 13px;
border: none;
cursor: pointer;
background-color: #259485;
color: #ffffff;
}
.submit button:hover {
background-color: #97d4c8;
transform: scale(1.05);
color: #00202f;
}
.options { .options {
display: block; display: block;
padding-bottom: 10px; padding-bottom: 10px;
@ -81,8 +81,3 @@ button:hover {
color: red; color: red;
display: none; /* will be made visible when an error occurs */ display: none; /* will be made visible when an error occurs */
} }
#cookieLinesResponse {
display: none;
width: 100%;
height: 50px;
}