This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-release.git


The following commit(s) were added to refs/heads/main by this push:
     new a445421  Use Bootstrap throughout
a445421 is described below

commit a4454216869161824103fba6031bf3060aff9346
Author: Sean B. Palmer <[email protected]>
AuthorDate: Fri Mar 14 16:59:32 2025 +0200

    Use Bootstrap throughout
---
 atr/blueprints/admin/templates/update-pmcs.html |   4 +-
 atr/static/css/atr-small.css                    | 353 ------------------------
 atr/static/css/atr.css                          |  44 +--
 atr/static/css/bootstrap.custom.css             |   4 +-
 atr/templates/candidate-create.html             |   9 -
 atr/templates/candidate-review.html             |   5 -
 atr/templates/dev-send-email.html               |   9 -
 atr/templates/docs-verify.html                  |  44 +--
 atr/templates/error.html                        |  50 +---
 atr/templates/keys-add.html                     | 136 +++------
 atr/templates/keys-review.html                  | 182 ++----------
 atr/templates/layouts/base.html                 |   6 +-
 atr/templates/notfound.html                     |  30 +-
 atr/templates/package-add.html                  | 269 +++++++-----------
 atr/templates/package-check.html                | 294 +++-----------------
 atr/templates/project-directory.html            | 143 ++++------
 atr/templates/project-view.html                 | 261 ++++++------------
 atr/templates/release-bulk.html                 | 315 +++++++--------------
 atr/templates/release-vote.html                 | 250 +++++------------
 atr/templates/vote-policy-add.html              |   9 -
 atr/templates/vote-policy-edit.html             |   9 -
 bootstrap/custom.scss                           |   4 +-
 22 files changed, 562 insertions(+), 1868 deletions(-)

diff --git a/atr/blueprints/admin/templates/update-pmcs.html 
b/atr/blueprints/admin/templates/update-pmcs.html
index 16714ed..3041b65 100644
--- a/atr/blueprints/admin/templates/update-pmcs.html
+++ b/atr/blueprints/admin/templates/update-pmcs.html
@@ -98,7 +98,7 @@
     const submitForm = async () => {
       const button = document.getElementById("submitButton");
       button.disabled = true;
-      document.body.style.cursor = 'wait'
+      document.body.style.cursor = "wait";
 
       const statusElement = document.getElementById("status");
       while (statusElement.firstChild) {
@@ -121,7 +121,7 @@
           addStatusMessage(statusElement, error, "error")
       } finally {
         button.disabled = false;
-        document.body.style.cursor = 'default'
+        document.body.style.cursor = "default";
       }
     };
 
diff --git a/atr/static/css/atr-small.css b/atr/static/css/atr-small.css
deleted file mode 100644
index 27d70a8..0000000
--- a/atr/static/css/atr-small.css
+++ /dev/null
@@ -1,353 +0,0 @@
-@font-face {
-    font-family: "Jost";
-    src: url("../webfonts/jost-v.woff2") format("woff2");
-    font-weight: 100 900;
-    font-style: normal;
-}
-
-@font-face {
-    font-family: "Inter";
-    src: url("../webfonts/inter-v.woff2") format("woff2");
-    font-weight: 100 900;
-    font-style: normal;
-}
-
-* {
-    margin: 0;
-    padding: 0;
-    box-sizing: border-box;
-}
-
-body {
-    font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe 
UI", "Oxygen", "Ubuntu", "Cantarell", "Open Sans", "Helvetica Neue", sans-serif;
-    -webkit-font-smoothing: antialiased;
-    font-size: 17px;
-    line-height: 24px;
-    font-variation-settings: "opsz" 22;
-    font-weight: 425;
-}
-
-input, textarea, select, option {
-    border-width: 2px !important;
-    border-color: #cccccc !important;
-    font-size: 17px !important;
-    font-weight: 425 !important;
-}
-
-input, textarea, button, select, option {
-    font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe 
UI", "Oxygen", "Ubuntu", "Cantarell", "Open Sans", "Helvetica Neue", sans-serif;
-    font-size: 17px;
-    line-height: 24px;
-    font-variation-settings: "opsz" 22;
-    font-weight: 425;
-}
-
-select, input[type="file"] {
-    padding: 6px 12px;
-}
-
-a {
-    font-weight: 450;
-}
-
-h1, h2, h3 {
-    font-weight: 475;
-    font-family: "Jost", system-ui, -apple-system, BlinkMacSystemFont, "Segoe 
UI", "Oxygen", "Ubuntu", "Cantarell", "Open Sans", "Helvetica Neue", sans-serif;
-}
-
-h1 {
-    margin-bottom: 2rem;
-}
-
-h2 {
-    border-bottom: 1px solid #d1d2d3;
-    padding-bottom: 0.5rem;
-    margin-top: 2.5rem;
-    margin-bottom: 1.5rem;
-}
-
-h3, p, ul, form {
-    margin-bottom: 1rem;
-}
-
-ul {
-    padding-left: 1rem;
-}
-
-table {
-    border-collapse: collapse;
-}
-
-table th {
-    text-align: left;
-}
-
-table td {
-    /* Not sure if we should keep it this way, but it seems pretty good */
-    font-family: ui-monospace, "SFMono-Regular", "Menlo", "Monaco", 
"Consolas", monospace;
-    word-break: break-all;
-    font-size: 0.9em;
-}
-
-table tr {
-    /* This doesn't always work; not clear why */
-    border-bottom: 1px solid #c1c2c3;
-}
-
-table tr:last-child {
-    border-bottom: none;
-}
-
-aside h1 {
-    font-size: 3rem;
-    line-height: 1.1;
-    text-align: center;
-}
-
-aside h1 span.rest {
-    color: #777777;
-}
-
-aside h3 {
-    margin-top: 1.5rem;
-}
-
-pre {
-    white-space: pre-wrap;
-
-    /* word-wrap: anywhere; */
-    word-break: break-all;
-}
-
-footer {
-    padding: 1rem;
-    background: #eeeeee;
-    font-size: 15px;
-    margin: 2rem;
-    color: #333333;
-    font-variation-settings: "opsz" 14;
-    border-radius: 0.5rem;
-    border: 2px solid #d1d2d3;
-    display: table;
-    text-align: center;
-}
-
-footer a {
-    color: #333333;
-    font-weight: 425;
-}
-
-footer a:visited {
-    color: #333333;
-    font-weight: 425;
-}
-
-footer p {
-    margin-bottom: 0;
-}
-
-input,
-textarea {
-    font-family: monospace;
-    padding: 0.5rem;
-}
-
-textarea {
-    width: 100%;
-    min-height: 200px;
-}
-
-form.striking {
-    background-color: #ffffee;
-    border: 2px solid #ddddbb;
-    padding: 1rem;
-    border-radius: 0.5rem;
-}
-
-.site-title {
-    text-decoration: none;
-    color: inherit;
-}
-
-span.warning {
-    color: #cc0000;
-    font-weight: 550;
-}
-
-.wrapper {
-    min-height: 100vh;
-    display: flex;
-    flex-direction: column;
-}
-
-.back-link {
-    display: inline-block;
-    margin-bottom: 1rem;
-    color: #0000ee;
-    text-decoration: none;
-}
-
-.back-link:hover {
-    text-decoration: underline;
-}
-
-.ribbon {
-    height: 8px;
-    background: linear-gradient(90deg, #282661 0%, #662f8f 20%, #9e2165 40%, 
#cb2138 60%, #ea7826 80%, #f7ae18 100%);
-}
-
-.content {
-    flex: 1;
-    display: flex;
-}
-
-.main-container {
-    flex: 1;
-    display: flex;
-    flex-direction: column;
-}
-
-.main-content {
-    flex: 1;
-    padding: 2rem;
-}
-
-.sidebar {
-    width: 250px;
-    background-color: #f6f7f8;
-    border-right: 1px solid #d1d2d3;
-    padding: 1rem;
-}
-
-.sidebar .user-section {
-    margin-bottom: 1.5rem;
-    text-align: center;
-    border-top: 1px solid #d1d2d3;
-    border-bottom: 1px solid #d1d2d3;
-    padding: 1.5rem 0;
-}
-
-.sidebar hr {
-    border: none;
-    border-top: 1px solid #999999;
-    margin: 1.5rem auto;
-    width: 62%;
-    height: 0;
-}
-
-.sidebar nav {
-    margin-top: 1.5rem;
-}
-
-.sidebar nav ul {
-    list-style: none;
-}
-
-.sidebar nav li {
-    margin-bottom: 1rem;
-}
-
-.hamburger {
-    display: none;
-    background: none;
-    border: none;
-    cursor: pointer;
-    padding: 0;
-    z-index: 100;
-}
-
-.hamburger span {
-    display: block;
-    width: 25px;
-    height: 3px;
-    background-color: #333333;
-    margin: 5px 0;
-    transition: 0.3s;
-}
-
-.nav-toggle {
-    display: none;
-}
-
-@media (width <= 768px) {
-    .hamburger {
-        display: block;
-        position: fixed;
-        top: 20px;
-        padding-left: 2rem;
-        transition: 0.3s;
-    }
-
-    .sidebar {
-        position: fixed;
-        left: -250px;
-        top: 20px;
-        bottom: 0;
-        transition: 0.3s;
-        z-index: 99;
-    }
-
-    /* Show sidebar when checkbox is checked */
-    .nav-toggle:checked ~ .sidebar {
-        left: 0;
-    }
-
-    /* Move hamburger with sidebar */
-    .nav-toggle:checked ~ .hamburger {
-        padding-left: calc(250px + 2rem); /* sidebar width + padding */
-    }
-
-    .nav-toggle:checked ~ .hamburger span:nth-child(1) {
-        transform: rotate(45deg) translate(5px, 5px);
-    }
-
-    .nav-toggle:checked ~ .hamburger span:nth-child(2) {
-        opacity: 0%;
-    }
-
-    .nav-toggle:checked ~ .hamburger span:nth-child(3) {
-        transform: rotate(-45deg) translate(7px, -7px);
-    }
-
-    .main-content {
-        margin-left: 0;
-        padding-top: 4rem;
-    }
-}
-
-/* Flash Messages */
-.flash-message {
-    padding: 1rem;
-    margin-bottom: 1rem;
-    border-radius: 4px;
-}
-
-.flash-warning {
-    background-color: #fff3cd;
-    border: 1px solid #ffeeba;
-    color: #856404;
-}
-
-.flash-error {
-    background-color: #f8d7da;
-    border: 1px solid #f5c6cb;
-    color: #721c24;
-}
-
-.flash-success {
-    background-color: #d4edda;
-    border: 1px solid #c3e6cb;
-    color: #155724;
-}
-
-.admin-content {
-    box-shadow: inset 0 0 0 10px #dc3545;
-}
-
-.warning-banner {
-    border: 2px solid #dc3545;
-    background: #ffcccc;
-    padding: 1rem;
-    margin: 0 0 2rem;
-    border-radius: 4px;
-}
diff --git a/atr/static/css/atr.css b/atr/static/css/atr.css
index a726a53..8615229 100644
--- a/atr/static/css/atr.css
+++ b/atr/static/css/atr.css
@@ -27,6 +27,13 @@ body {
     font-weight: 425;
 }
 
+input, textarea, select, option {
+    border-width: 2px !important;
+    border-color: #cccccc !important;
+    font-size: 17px !important;
+    font-weight: 425 !important;
+}
+
 input, textarea, button, select, option {
     font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe 
UI", "Oxygen", "Ubuntu", "Cantarell", "Open Sans", "Helvetica Neue", sans-serif;
     font-size: 17px;
@@ -35,6 +42,12 @@ input, textarea, button, select, option {
     font-weight: 425;
 }
 
+label[for] {
+    font-weight: 450;
+    border-bottom: 1px dashed #d1d2d3;
+    cursor: pointer;
+}
+
 select, input[type="file"] {
     padding: 6px 12px;
 }
@@ -67,31 +80,15 @@ ul {
     padding-left: 1rem;
 }
 
-label {
-    font-weight: 500;
-    border-bottom: 1px dashed #b1b2b3;
-    padding-bottom: 0.5rem;
-    cursor: pointer;
-}
-
 table {
-    width: 100%;
     border-collapse: collapse;
 }
 
 table th {
     text-align: left;
-    padding: 0.5rem;
-    font-weight: 500;
-    width: 30%;
-    color: #333333;
 }
 
 table td {
-    padding: 0.5rem;
-
-    /* Almost all of our tables have data in them */
-
     /* Not sure if we should keep it this way, but it seems pretty good */
     font-family: ui-monospace, "SFMono-Regular", "Menlo", "Monaco", 
"Consolas", monospace;
     word-break: break-all;
@@ -155,21 +152,6 @@ footer p {
     margin-bottom: 0;
 }
 
-button {
-    margin-top: 1rem;
-    padding: 0.5rem 1rem;
-    background: #004477;
-    color: white;
-    border: none;
-    border-radius: 4px;
-    cursor: pointer;
-    font-weight: 500;
-}
-
-button:hover {
-    background: #003366;
-}
-
 input,
 textarea {
     font-family: monospace;
diff --git a/atr/static/css/bootstrap.custom.css 
b/atr/static/css/bootstrap.custom.css
index e36516d..295092b 100644
--- a/atr/static/css/bootstrap.custom.css
+++ b/atr/static/css/bootstrap.custom.css
@@ -6135,7 +6135,7 @@ textarea.form-control-lg {
   --bs-table-color-state: initial;
   --bs-table-bg-state: initial;
   --bs-table-color: var(--bs-emphasis-color);
-  --bs-table-bg: var(--bs-body-bg);
+  --bs-table-bg: transparent;
   --bs-table-border-color: var(--bs-border-color);
   --bs-table-accent-bg: transparent;
   --bs-table-striped-color: var(--bs-emphasis-color);
@@ -11445,7 +11445,7 @@ textarea.form-control-lg {
   }
 }
 th {
-  color: #555;
+  color: #212529;
   font-weight: 525;
 }
 
diff --git a/atr/templates/candidate-create.html 
b/atr/templates/candidate-create.html
index 8b4e490..1861070 100644
--- a/atr/templates/candidate-create.html
+++ b/atr/templates/candidate-create.html
@@ -8,15 +8,6 @@
   Create a new release candidate.
 {% endblock description %}
 
-{% block theme_css %}
-  <link rel="stylesheet"
-        href="{{ url_for('static', filename='css/atr-small.css') }}" />
-{% endblock theme_css %}
-
-{% block stylesheets %}
-  {{ super() }}
-{% endblock stylesheets %}
-
 {% block content %}
   <h1>Create release candidate</h1>
   <p>
diff --git a/atr/templates/candidate-review.html 
b/atr/templates/candidate-review.html
index f0a094c..fe08b65 100644
--- a/atr/templates/candidate-review.html
+++ b/atr/templates/candidate-review.html
@@ -8,11 +8,6 @@
   Release candidates to which you have access.
 {% endblock description %}
 
-{% block theme_css %}
-  <link rel="stylesheet"
-        href="{{ url_for('static', filename='css/atr-small.css') }}" />
-{% endblock theme_css %}
-
 {% block stylesheets %}
   {{ super() }}
   <style>
diff --git a/atr/templates/dev-send-email.html 
b/atr/templates/dev-send-email.html
index 886d25a..1799724 100644
--- a/atr/templates/dev-send-email.html
+++ b/atr/templates/dev-send-email.html
@@ -8,15 +8,6 @@
   Test email sending functionality.
 {% endblock description %}
 
-{% block theme_css %}
-  <link rel="stylesheet"
-        href="{{ url_for('static', filename='css/atr-small.css') }}" />
-{% endblock theme_css %}
-
-{% block stylesheets %}
-  {{ super() }}
-{% endblock stylesheets %}
-
 {% block content %}
   <h1>Test email sending</h1>
   <p>
diff --git a/atr/templates/docs-verify.html b/atr/templates/docs-verify.html
index e51bf9f..d95cb0b 100644
--- a/atr/templates/docs-verify.html
+++ b/atr/templates/docs-verify.html
@@ -8,30 +8,6 @@
   Instructions for verifying an artifact.
 {% endblock description %}
 
-{% block stylesheets %}
-  {{ super() }}
-  <style>
-      .verification-steps {
-          margin-top: 1rem;
-      }
-
-      .verification-steps h3 {
-          margin-top: 1.5rem;
-          margin-bottom: 0.5rem;
-      }
-
-      .verification-steps pre {
-          background-color: #f5f5f5;
-          padding: 0.8rem;
-          border-radius: 4px;
-          overflow-x: auto;
-          font-family: monospace;
-          margin: 0.5rem 0;
-          position: relative;
-      }
-  </style>
-{% endblock stylesheets %}
-
 {% block content %}
   <p>
     <a href="{{ url_for('root_candidate_review') }}" class="back-link">← Back 
to release candidates</a>
@@ -42,28 +18,28 @@
     Follow these steps to verify the downloaded artifact <code>{{ filename 
}}</code>.
   </p>
 
-  <div class="verification-steps">
-    <h3>1. Download the artifact and signature</h3>
+  <div class="mt-3">
+    <h3 class="mt-4 mb-2">1. Download the artifact and signature</h3>
     <p>Use the download buttons on the release candidate page to download both 
the artifact and its signature.</p>
 
-    <h3>2. Verify signature with GPG</h3>
+    <h3 class="mt-4 mb-2">2. Verify signature with GPG</h3>
     <p>Run the following command to verify the signature:</p>
-    <pre>{% if has_signature %}gpg --verify {{ filename }}.asc {{ filename 
}}{% else %}No signature available for this artifact.{% endif %}</pre>
+    <pre class="bg-light p-3 rounded overflow-auto my-2">{% if has_signature 
%}gpg --verify {{ filename }}.asc {{ filename }}{% else %}No signature 
available for this artifact.{% endif %}</pre>
 
-    <h3>3. Verify SHA3-256 hash</h3>
+    <h3 class="mt-4 mb-2">3. Verify SHA3-256 hash</h3>
     <p>Run the following command and compare the output with the hash 
displayed on the release candidate page:</p>
-    <pre>sha3sum -a 256 {{ filename }}</pre>
+    <pre class="bg-light p-3 rounded overflow-auto my-2">sha3sum -a 256 {{ 
filename }}</pre>
     {% if artifact_sha3 %}
       <p>Expected SHA3-256 hash:</p>
-      <pre>{{ artifact_sha3 }}</pre>
+      <pre class="bg-light p-3 rounded overflow-auto my-2">{{ artifact_sha3 
}}</pre>
     {% endif %}
 
-    <h3>4. Verify SHA-512 hash</h3>
+    <h3 class="mt-4 mb-2">4. Verify SHA-512 hash</h3>
     <p>Run the following command and compare the output with the hash 
displayed on the release candidate page:</p>
-    <pre>sha512sum {{ filename }}</pre>
+    <pre class="bg-light p-3 rounded overflow-auto my-2">sha512sum {{ filename 
}}</pre>
     {% if sha512 %}
       <p>Expected SHA-512 hash:</p>
-      <pre>{{ sha512 }}</pre>
+      <pre class="bg-light p-3 rounded overflow-auto my-2">{{ sha512 }}</pre>
     {% endif %}
   </div>
 {% endblock content %}
diff --git a/atr/templates/error.html b/atr/templates/error.html
index 460d902..763881a 100644
--- a/atr/templates/error.html
+++ b/atr/templates/error.html
@@ -8,49 +8,11 @@
   An error occurred while processing your request.
 {% endblock description %}
 
-{% block stylesheets %}
-  {{ super() }}
-  <style>
-      .error-container {
-          margin: 20px 0;
-          padding: 15px;
-          border: 1px solid #dc3545;
-          border-radius: 4px;
-          background-color: #f8d7da;
-      }
-
-      .error-title {
-          color: #721c24;
-          margin-top: 0;
-      }
-
-      .error-details {
-          margin-top: 20px;
-      }
-
-      .error-traceback {
-          background-color: #f5f5f5;
-          padding: 15px;
-          overflow-x: auto;
-          font-family: monospace;
-          font-size: 13px;
-          line-height: 1.5;
-          color: #333;
-          border: 1px solid #ccc;
-          border-radius: 4px;
-      }
-
-      .actions {
-          margin-top: 20px;
-      }
-  </style>
-{% endblock stylesheets %}
-
 {% block content %}
   <div>
     <h1>Error</h1>
-    <div class="error-container">
-      <h2 class="error-title">{{ error }}</h2>
+    <div class="my-4 p-3 border border-danger rounded bg-danger-subtle">
+      <h2 class="text-danger-emphasis mt-0">{{ error }}</h2>
       <p>An error occurred while processing your request. This has been logged 
and will be addressed.</p>
 
       {% if status_code %}
@@ -60,15 +22,15 @@
       {% endif %}
 
       {% if traceback %}
-        <div class="error-details">
+        <div class="mt-4">
           <h3>Technical Details</h3>
-          <pre class="error-traceback">{{ traceback }}</pre>
+          <pre class="bg-light p-3 overflow-auto font-monospace border 
rounded">{{ traceback }}</pre>
         </div>
       {% endif %}
     </div>
 
-    <div class="actions">
-      <a href="{{ url_for('root') }}" class="button">Return to Home</a>
+    <div class="mt-4">
+      <a href="{{ url_for('root') }}" class="btn btn-primary">Return to 
Home</a>
     </div>
   </div>
 {% endblock content %}
diff --git a/atr/templates/keys-add.html b/atr/templates/keys-add.html
index 543c501..e2f2495 100644
--- a/atr/templates/keys-add.html
+++ b/atr/templates/keys-add.html
@@ -8,81 +8,6 @@
   Add a public signing key to your account.
 {% endblock description %}
 
-{% block stylesheets %}
-  {{ super() }}
-  <style>
-      .form-group {
-          margin-bottom: 1rem;
-      }
-
-      .form-group label {
-          display: inline-block;
-          margin-bottom: 1rem;
-          padding: 0;
-      }
-
-      .error-message {
-          color: #dc3545;
-          margin-top: 0.25rem;
-      }
-
-      .key-info {
-          margin-top: 1rem;
-          padding: 1rem;
-          background: #f8f9fa;
-          border-radius: 4px;
-      }
-
-      .key-info h3 {
-          margin-top: 0;
-      }
-
-      .key-info dl {
-          margin: 0;
-          display: grid;
-          grid-template-columns: auto 1fr;
-          gap: 0.5rem 1rem;
-      }
-
-      .key-info dt {
-          font-weight: bold;
-      }
-
-      .key-info dd {
-          margin: 0;
-      }
-
-      .navigation {
-          margin-top: 2rem;
-      }
-
-      .pmc-checkboxes {
-          display: grid;
-          grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
-          gap: 0.5rem;
-          margin-top: 0.5rem;
-          margin-bottom: 0.5rem;
-      }
-
-      .checkbox-item {
-          display: flex;
-          align-items: center;
-          gap: 0.5rem;
-      }
-
-      .checkbox-item label {
-          border-bottom: none;
-          margin-bottom: 0;
-          font-weight: normal;
-      }
-
-      .checkbox-item input[type="checkbox"] {
-          width: 1rem;
-          height: 1rem;
-      }
-  </style>
-{% endblock stylesheets %}
-
 {% block content %}
   <h1>Add signing key</h1>
   <p class="intro">Add your public key to use for signing release 
artifacts.</p>
@@ -95,72 +20,79 @@
 
   {% if key_info %}
     <h2>Key results</h2>
-    <div class="key-info">
-      <h3>Success: Added Key</h3>
-      <dl>
-        <dt>Key ID</dt>
-        <dd>
+    <div class="mt-3 p-3 bg-light rounded">
+      <h3 class="mt-0">Success: Added Key</h3>
+      <dl class="row mb-0">
+        <dt class="col-sm-3 fw-bold">Key ID</dt>
+        <dd class="col-sm-9 mb-2">
           {{ key_info.key_id }}
         </dd>
-        <dt>Fingerprint</dt>
-        <dd>
+        <dt class="col-sm-3 fw-bold">Fingerprint</dt>
+        <dd class="col-sm-9 mb-2">
           {{ key_info.fingerprint }}
         </dd>
-        <dt>User ID</dt>
-        <dd>
+        <dt class="col-sm-3 fw-bold">User ID</dt>
+        <dd class="col-sm-9 mb-2">
           {{ key_info.user_id }}
         </dd>
-        <dt>Created</dt>
-        <dd>
+        <dt class="col-sm-3 fw-bold">Created</dt>
+        <dd class="col-sm-9 mb-2">
           {{ key_info.creation_date }}
         </dd>
-        <dt>Expires</dt>
-        <dd>
+        <dt class="col-sm-3 fw-bold">Expires</dt>
+        <dd class="col-sm-9 mb-2">
           {{ key_info.expiration_date or 'Never' }}
         </dd>
-        <dt>Key Data</dt>
-        <dd>
-          <pre>{{ key_info.data }}</pre>
+        <dt class="col-sm-3 fw-bold">Key Data</dt>
+        <dd class="col-sm-9 mb-2">
+          <pre class="mb-0">{{ key_info.data }}</pre>
         </dd>
       </dl>
     </div>
   {% endif %}
 
   <form method="post" class="striking">
-    <div class="form-group">
-      <label for="public_key">Public Key:</label>
+    <div class="mb-4">
+      <div class="mb-3">
+        <label for="public_key" class="form-label">Public Key:</label>
+      </div>
       <textarea id="public_key"
                 name="public_key"
+                class="form-control mb-2"
+                rows="8"
                 required
                 placeholder="Paste your public key here (in ASCII-armored 
format)"
                 aria-describedby="key-help"></textarea>
-      <small id="key-help">
+      <small id="key-help" class="form-text text-muted">
         Your public key should be in ASCII-armored format, starting with 
"-----BEGIN PGP PUBLIC KEY BLOCK-----"
       </small>
     </div>
 
     {% if user_pmcs %}
-      <div class="form-group">
-        <label>Associate with projects:</label>
-        <div class="pmc-checkboxes">
+      <div class="mb-4">
+        <div class="mb-3">
+          <label class="form-label">Associate with projects:</label>
+        </div>
+        <div class="d-flex flex-wrap gap-3 mb-2">
           {% for pmc in user_pmcs|sort(attribute='name') %}
-            <div class="checkbox-item">
+            <div class="form-check d-flex align-items-center gap-2">
               <input type="checkbox"
+                     class="form-check-input"
                      id="pmc_{{ pmc.name }}"
                      name="selected_pmcs"
                      value="{{ pmc.name }}" />
-              <label for="pmc_{{ pmc.name }}">{{ pmc.display_name }}</label>
+              <label class="form-check-label mb-0" for="pmc_{{ pmc.name }}">{{ 
pmc.display_name }}</label>
             </div>
           {% endfor %}
         </div>
-        <small>You must associate your key with at least one PMC of which you 
are a member.</small>
+        <small class="form-text text-muted">You must associate your key with 
at least one PMC of which you are a member.</small>
       </div>
     {% else %}
-      <div class="error-message">
+      <div class="text-danger mt-1">
         <p>You must be a member of at least one PMC to add a signing key.</p>
       </div>
     {% endif %}
 
-    <button type="submit">Add Key</button>
+    <button type="submit" class="btn btn-primary">Add Key</button>
   </form>
 {% endblock content %}
diff --git a/atr/templates/keys-review.html b/atr/templates/keys-review.html
index 30e27a3..a7a5b7a 100644
--- a/atr/templates/keys-review.html
+++ b/atr/templates/keys-review.html
@@ -8,136 +8,6 @@
   Review your signing keys.
 {% endblock description %}
 
-{% block stylesheets %}
-  {{ super() }}
-  <style>
-      .existing-keys {
-          margin-bottom: 2rem;
-          padding: 1rem 2rem 2rem 2rem;
-          background: #f8f9fa;
-          border-radius: 4px;
-      }
-
-      .keys-grid {
-          display: grid;
-          gap: 1.5rem;
-      }
-
-      .key-card {
-          background: white;
-          border: 1px solid #d1d2d3;
-          border-radius: 4px;
-          overflow: hidden;
-          padding: 1rem;
-      }
-
-      .key-card table {
-          margin: 0;
-      }
-
-      .key-card td {
-          word-break: break-all;
-      }
-
-      .key-card h3 {
-          margin-top: 0;
-          margin-bottom: 1rem;
-      }
-
-      .delete-key-form {
-          margin-top: 1rem;
-      }
-
-      .delete-button {
-          background: #dc3545;
-          color: white;
-          border: none;
-          padding: 0.5rem 1rem;
-          border-radius: 4px;
-          cursor: pointer;
-      }
-
-      .delete-button:hover {
-          background: #c82333;
-      }
-
-      .navigation {
-          margin-top: 2rem;
-      }
-
-      .success-message {
-          color: #28a745;
-          margin: 1rem 0;
-          padding: 1rem;
-          background: #d4edda;
-          border-radius: 4px;
-      }
-
-      .key-details {
-          margin-top: 1rem;
-          padding: 1rem;
-          background: #f8f9fa;
-          border-radius: 4px;
-      }
-
-      .key-details summary {
-          font-weight: bold;
-          cursor: pointer;
-      }
-
-      .key-details pre {
-          margin-top: 1rem;
-          white-space: pre-wrap;
-      }
-
-      .status-message {
-          margin-top: 2rem;
-          padding: 1rem;
-          background: #f8f9fa;
-          border-radius: 4px;
-      }
-
-      .status-message.success {
-          background: #d4edda;
-      }
-
-      .status-message.error {
-          background: #f8d7da;
-      }
-
-      .expiry-warning {
-          color: #e67e22;
-          font-weight: bold;
-      }
-
-      .expiry-error {
-          color: #e74c3c;
-          font-weight: bold;
-      }
-
-      .notice-badge {
-          display: inline-block;
-          padding: 0.25rem 0.5rem;
-          border-radius: 3px;
-          font-size: 0.8em;
-          margin-left: 0.5rem;
-          font-weight: 500;
-      }
-
-      .notice-badge.warning {
-          background-color: #fff3cd;
-          color: #856404;
-          border: 1px solid #ffeeba;
-      }
-
-      .notice-badge.error {
-          background-color: #f8d7da;
-          color: #721c24;
-          border: 1px solid #f5c6cb;
-      }
-  </style>
-{% endblock stylesheets %}
-
 {% block content %}
   <h1>Your signing keys</h1>
   <p class="intro">Review your public keys used for signing release 
artifacts.</p>
@@ -149,38 +19,38 @@
   </div>
 
   {% if user_keys %}
-    <div class="existing-keys">
-      <div class="keys-grid">
+    <div class="mb-5 p-4 bg-light rounded">
+      <div class="d-grid gap-4">
         {% for key in user_keys %}
-          <div class="key-card">
-            <table>
+          <div class="card p-3 border">
+            <table class="mb-0">
               <tbody>
                 <tr>
-                  <th>Fingerprint</th>
-                  <td>{{ key.fingerprint }}</td>
+                  <th class="p-2 text-dark">Fingerprint</th>
+                  <td class="text-break">{{ key.fingerprint }}</td>
                 </tr>
                 <tr>
-                  <th>Key Type</th>
-                  <td>{{ algorithms[key.algorithm] }} ({{ key.length }} 
bits)</td>
+                  <th class="p-2 text-dark">Key Type</th>
+                  <td class="text-break">{{ algorithms[key.algorithm] }} ({{ 
key.length }} bits)</td>
                 </tr>
                 <tr>
-                  <th>Created</th>
-                  <td>{{ key.created.strftime("%Y-%m-%d %H:%M:%S") }}</td>
+                  <th class="p-2 text-dark">Created</th>
+                  <td class="text-break">{{ key.created.strftime("%Y-%m-%d 
%H:%M:%S") }}</td>
                 </tr>
                 <tr>
-                  <th>Expires</th>
-                  <td>
+                  <th class="p-2 text-dark">Expires</th>
+                  <td class="text-break">
                     {% if key.expires %}
                       {% set days_until_expiry = (key.expires - now).days %}
                       {% if days_until_expiry < 0 %}
-                        <span class="expiry-error">
+                        <span class="text-danger fw-bold">
                           {{ key.expires.strftime("%Y-%m-%d %H:%M:%S") }}
-                          <span class="notice-badge error">Expired</span>
+                          <span class="badge bg-danger text-white 
ms-2">Expired</span>
                         </span>
                       {% elif days_until_expiry <= 30 %}
-                        <span class="expiry-warning">
+                        <span class="text-warning fw-bold">
                           {{ key.expires.strftime("%Y-%m-%d %H:%M:%S") }}
-                          <span class="notice-badge warning">Expires in {{ 
days_until_expiry }} days</span>
+                          <span class="badge bg-warning text-dark 
ms-2">Expires in {{ days_until_expiry }} days</span>
                         </span>
                       {% else %}
                         {{ key.expires.strftime("%Y-%m-%d %H:%M:%S") }}
@@ -191,12 +61,12 @@
                   </td>
                 </tr>
                 <tr>
-                  <th>User ID</th>
-                  <td>{{ key.declared_uid or 'Not specified' }}</td>
+                  <th class="p-2 text-dark">User ID</th>
+                  <td class="text-break">{{ key.declared_uid or 'Not 
specified' }}</td>
                 </tr>
                 <tr>
-                  <th>Associated projects</th>
-                  <td>
+                  <th class="p-2 text-dark">Associated projects</th>
+                  <td class="text-break">
                     {% if key.pmcs %}
                       {{ key.pmcs|map(attribute='name') |join(', ') }}
                     {% else %}
@@ -208,16 +78,14 @@
             </table>
 
             <!-- TODO: We could link to a downloadable version of the key 
instead -->
-            <details class="key-details">
-              <summary>View whole key</summary>
-              <pre class="key-text">{{ key.ascii_armored_key }}</pre>
+            <details class="mt-3 p-3 bg-light rounded">
+              <summary class="fw-bold">View whole key</summary>
+              <pre class="mt-3">{{ key.ascii_armored_key }}</pre>
             </details>
 
-            <form method="post"
-                  action="{{ url_for('root_keys_delete') }}"
-                  class="delete-key-form">
+            <form method="post" action="{{ url_for('root_keys_delete') }}" 
class="mt-3">
               <input type="hidden" name="fingerprint" value="{{ 
key.fingerprint }}" />
-              <button type="submit" class="delete-button">Delete key</button>
+              <button type="submit" class="btn btn-danger">Delete key</button>
             </form>
           </div>
         {% endfor %}
diff --git a/atr/templates/layouts/base.html b/atr/templates/layouts/base.html
index bfcd2d5..20bbe9d 100644
--- a/atr/templates/layouts/base.html
+++ b/atr/templates/layouts/base.html
@@ -13,10 +13,8 @@
     {% block stylesheets %}
       <link rel="stylesheet"
             href="{{ url_for('static', filename='css/normalize.css') }}" />
-      {% block theme_css %}
-        <link rel="stylesheet"
-              href="{{ url_for('static', filename='css/atr.css') }}" />
-      {% endblock theme_css %}
+      <link rel="stylesheet"
+            href="{{ url_for('static', filename='css/atr.css') }}" />
       <link rel="stylesheet"
             href="{{ url_for('static', filename='css/fontawesome.all.min.css') 
}}" />
       <link rel="stylesheet"
diff --git a/atr/templates/notfound.html b/atr/templates/notfound.html
index 86077f7..5b3f925 100644
--- a/atr/templates/notfound.html
+++ b/atr/templates/notfound.html
@@ -8,33 +8,11 @@
   An error occurred while processing your request.
 {% endblock description %}
 
-{% block stylesheets %}
-  {{ super() }}
-  <style>
-      .error-container {
-          margin: 20px 0;
-          padding: 15px;
-          border: 1px solid #dc3545;
-          border-radius: 4px;
-          background-color: #f8d7da;
-      }
-
-      .error-title {
-          color: #721c24;
-          margin-top: 0;
-      }
-
-      .actions {
-          margin-top: 20px;
-      }
-  </style>
-{% endblock stylesheets %}
-
 {% block content %}
   <div>
     <h1>Error</h1>
-    <div class="error-container">
-      <h2 class="error-title">{{ error }}</h2>
+    <div class="my-4 p-3 border border-danger rounded bg-danger-subtle">
+      <h2 class="text-danger-emphasis mt-0">{{ error }}</h2>
       <p>
         The requested URL was not found on the server. If you entered the URL 
manually please check your spelling and try again.
       </p>
@@ -46,8 +24,8 @@
       {% endif %}
     </div>
 
-    <div class="actions">
-      <a href="{{ url_for('root') }}" class="button">Return to Home</a>
+    <div class="mt-4">
+      <a href="{{ url_for('root') }}" class="btn btn-primary">Return to 
Home</a>
     </div>
   </div>
 {% endblock content %}
diff --git a/atr/templates/package-add.html b/atr/templates/package-add.html
index 655dfe3..1a76ce3 100644
--- a/atr/templates/package-add.html
+++ b/atr/templates/package-add.html
@@ -8,121 +8,6 @@
   Add a package to an existing release.
 {% endblock description %}
 
-{% block stylesheets %}
-  {{ super() }}
-  <style>
-      .form-table {
-          width: 100%;
-      }
-
-      .form-table th {
-          width: 200px;
-          text-align: right;
-          padding-right: 1rem;
-          vertical-align: top;
-          font-weight: 500;
-      }
-
-      .form-table td {
-          vertical-align: top;
-      }
-
-      .form-table label {
-          border-bottom: none;
-          padding-bottom: 0;
-      }
-
-      select,
-      input[type="file"] {
-          display: block;
-          margin-bottom: 0.5rem;
-      }
-
-      .help-text {
-          color: #666;
-          font-size: 0.9em;
-          display: block;
-          margin-top: 0.25rem;
-      }
-
-      .error-message {
-          color: #dc3545;
-          margin-top: 0.25rem;
-      }
-
-      button {
-          margin-top: 1rem;
-      }
-
-      button:disabled {
-          opacity: 0.5;
-          cursor: not-allowed;
-      }
-
-      .radio-group {
-          margin-bottom: 0.5rem;
-      }
-
-      .radio-group div {
-          margin: 0.5rem 0;
-      }
-
-      .radio-group label {
-          margin-left: 0.5rem;
-          cursor: pointer;
-      }
-
-      .radio-group input[type="radio"] {
-          cursor: pointer;
-      }
-
-      .checkbox-group {
-          margin-bottom: 0.5rem;
-      }
-
-      .checkbox-group div {
-          margin: 0.5rem 0;
-      }
-
-      .checkbox-group label {
-          margin-left: 0.5rem;
-          cursor: pointer;
-      }
-
-      .checkbox-group input[type="checkbox"] {
-          cursor: pointer;
-      }
-
-      .form-separator {
-          margin: 2rem 0;
-          border-top: 1px solid #ddd;
-          text-align: center;
-          position: relative;
-      }
-
-      .form-separator span {
-          background: #fff;
-          padding: 0 1rem;
-          color: #666;
-          position: relative;
-          top: -0.75rem;
-      }
-
-      input[type="url"],
-      input[type="number"] {
-          width: 100%;
-          max-width: 600px;
-          padding: 0.375rem;
-          border: 1px solid #ced4da;
-          border-radius: 0.25rem;
-      }
-
-      input[type="number"] {
-          width: 100px;
-      }
-  </style>
-{% endblock stylesheets %}
-
 {% block content %}
   <h1>Add package</h1>
   <p class="intro">
@@ -132,14 +17,14 @@
 
   <form method="post" enctype="multipart/form-data" class="striking">
     <input type="hidden" name="form_type" value="single" />
-    <table class="form-table">
+    <table class="table">
       <tbody>
         <tr>
-          <th>
+          <th class="text-end pe-3 align-top">
             <label for="release_key">Release:</label>
           </th>
-          <td>
-            <select id="release_key" name="release_key" required>
+          <td class="align-top">
+            <select id="release_key" name="release_key" class="form-select 
mb-2" required>
               <option value="">Select a release...</option>
               {% for release in releases %}
                 <option value="{{ release.storage_key }}"
@@ -148,107 +33,124 @@
                 </option>
               {% endfor %}
             </select>
-            {% if not releases %}<p class="error-message">No releases found 
that you can add a package to.</p>{% endif %}
+            {% if not releases %}<p class="text-danger mt-1">No releases found 
that you can add a package to.</p>{% endif %}
           </td>
         </tr>
 
         <tr>
-          <th>
+          <th class="text-end pe-3 align-top">
             <label for="release_artifact">Release candidate artifact:</label>
           </th>
-          <td>
+          <td class="align-top">
             <input type="file"
                    id="release_artifact"
                    name="release_artifact"
+                   class="form-control mb-2"
                    required
                    
accept="application/gzip,application/x-gzip,application/x-tar,application/zip,application/java-archive,.tar.gz,.tgz,.zip,.jar"
                    aria-describedby="artifact-help" />
-            <span id="artifact-help" class="help-text">Upload the release 
candidate archive (tar.gz, zip, or jar)</span>
+            <span id="artifact-help" class="form-text text-muted">Upload the 
release candidate archive (tar.gz, zip, or jar)</span>
           </td>
         </tr>
 
         <tr>
-          <th>
+          <th class="text-end pe-3 align-top">
             <label>Artifact type:</label>
           </th>
-          <td>
-            <div class="radio-group">
-              <div>
+          <td class="align-top">
+            <div class="mb-2">
+              <div class="form-check my-2">
                 <input type="radio"
+                       class="form-check-input"
                        id="source"
                        name="artifact_type"
                        value="source"
                        required
                        checked />
-                <label for="source">Source archive</label>
+                <label class="form-check-label" for="source">Source 
archive</label>
               </div>
-              <div>
-                <input type="radio" id="binary" name="artifact_type" 
value="binary" />
-                <label for="binary">Binary archive</label>
+              <div class="form-check my-2">
+                <input type="radio"
+                       class="form-check-input"
+                       id="binary"
+                       name="artifact_type"
+                       value="binary" />
+                <label class="form-check-label" for="binary">Binary 
archive</label>
               </div>
-              <div>
+              <div class="form-check my-2">
                 <input type="radio"
+                       class="form-check-input"
                        id="reproducible"
                        name="artifact_type"
                        value="reproducible" />
-                <label for="reproducible">Reproducible binary archive</label>
+                <label class="form-check-label" 
for="reproducible">Reproducible binary archive</label>
               </div>
             </div>
           </td>
         </tr>
 
         <tr>
-          <th>
+          <th class="text-end pe-3 align-top">
             <label for="release_checksum">SHA2-512 hash file:</label>
           </th>
-          <td>
+          <td class="align-top">
             <input type="file"
                    id="release_checksum"
                    name="release_checksum"
+                   class="form-control mb-2"
                    accept=".sha512,.sha,.txt"
                    aria-describedby="checksum-help" />
-            <span id="checksum-help" class="help-text">Optional: Upload the 
SHA-512 checksum file for verification</span>
+            <span id="checksum-help" class="form-text text-muted">Optional: 
Upload the SHA-512 checksum file for verification</span>
           </td>
         </tr>
 
         <tr>
-          <th>
+          <th class="text-end pe-3 align-top">
             <label for="release_signature">Detached signature:</label>
           </th>
-          <td>
+          <td class="align-top">
             <input type="file"
                    id="release_signature"
                    name="release_signature"
+                   class="form-control mb-2"
                    accept="application/pgp-signature,.asc"
                    aria-describedby="signature-help" />
-            <span id="signature-help" class="help-text">Optional: Upload the 
detached GPG signature (.asc) file</span>
+            <span id="signature-help" class="form-text text-muted">Optional: 
Upload the detached GPG signature (.asc) file</span>
           </td>
         </tr>
 
         <tr>
           <td></td>
           <td>
-            <button type="submit" {% if not releases %}disabled{% endif %}>Add 
package</button>
+            <button type="submit"
+                    class="btn btn-primary mt-3"
+                    {% if not releases %}disabled{% endif %}>Add 
package</button>
           </td>
         </tr>
       </tbody>
     </table>
   </form>
 
-  <div class="form-separator">
-    <span>Or add multiple packages from a URL</span>
+  <div class="my-5 position-relative">
+    <hr class="border-2 opacity-25" />
+    <div class="position-absolute top-50 start-50 translate-middle bg-white 
px-4 fs-5">
+      Or add multiple packages from a URL
+    </div>
   </div>
 
   <form method="post" class="striking">
     <input type="hidden" name="form_type" value="bulk" />
-    <table class="form-table">
+    <table class="table">
       <tbody>
         <tr>
-          <th>
+          <th class="text-end pe-3 align-top">
             <label for="bulk_release_key">Release:</label>
           </th>
-          <td>
-            <select id="bulk_release_key" name="release_key" required>
+          <td class="align-top">
+            <select id="bulk_release_key"
+                    name="release_key"
+                    class="form-select mb-2"
+                    required>
               <option value="">Select a release...</option>
               {% for release in releases %}
                 <option value="{{ release.storage_key }}"
@@ -257,84 +159,103 @@
                 </option>
               {% endfor %}
             </select>
-            {% if not releases %}<p class="error-message">No releases found 
that you can add packages to.</p>{% endif %}
+            {% if not releases %}<p class="text-danger mt-1">No releases found 
that you can add packages to.</p>{% endif %}
           </td>
         </tr>
 
         <tr>
-          <th>
+          <th class="text-end pe-3 align-top">
             <label for="bulk_url">URL:</label>
           </th>
-          <td>
+          <td class="align-top">
             <input type="url"
                    id="bulk_url"
                    name="url"
+                   class="form-control mb-2"
                    required
                    placeholder="https://example.org/path/to/packages/";
                    aria-describedby="url-help" />
-            <span id="url-help" class="help-text">Enter the URL of the 
directory containing release packages</span>
+            <span id="url-help" class="form-text text-muted">Enter the URL of 
the directory containing release packages</span>
           </td>
         </tr>
 
         <tr>
-          <th>
+          <th class="text-end pe-3 align-top">
             <label>File types:</label>
           </th>
-          <td>
-            <div class="checkbox-group">
-              <div>
+          <td class="align-top">
+            <div class="mb-2">
+              <div class="form-check my-2">
                 <input type="checkbox"
+                       class="form-check-input"
                        id="type_targz"
                        name="file_types"
                        value=".tar.gz"
                        checked />
-                <label for="type_targz">.tar.gz files</label>
+                <label class="form-check-label" for="type_targz">.tar.gz 
files</label>
               </div>
-              <div>
-                <input type="checkbox" id="type_tgz" name="file_types" 
value=".tgz" checked />
-                <label for="type_tgz">.tgz files</label>
+              <div class="form-check my-2">
+                <input type="checkbox"
+                       class="form-check-input"
+                       id="type_tgz"
+                       name="file_types"
+                       value=".tgz"
+                       checked />
+                <label class="form-check-label" for="type_tgz">.tgz 
files</label>
               </div>
-              <div>
-                <input type="checkbox" id="type_zip" name="file_types" 
value=".zip" />
-                <label for="type_zip">.zip files</label>
+              <div class="form-check my-2">
+                <input type="checkbox"
+                       class="form-check-input"
+                       id="type_zip"
+                       name="file_types"
+                       value=".zip" />
+                <label class="form-check-label" for="type_zip">.zip 
files</label>
               </div>
-              <div>
-                <input type="checkbox" id="type_jar" name="file_types" 
value=".jar" />
-                <label for="type_jar">.jar files</label>
+              <div class="form-check my-2">
+                <input type="checkbox"
+                       class="form-check-input"
+                       id="type_jar"
+                       name="file_types"
+                       value=".jar" />
+                <label class="form-check-label" for="type_jar">.jar 
files</label>
               </div>
             </div>
           </td>
         </tr>
 
         <tr>
-          <th>
+          <th class="text-end pe-3 align-top">
             <label for="bulk_max_depth">Maximum depth:</label>
           </th>
-          <td>
+          <td class="align-top">
             <input type="number"
                    id="bulk_max_depth"
                    name="max_depth"
+                   class="form-control mb-2 w-25"
                    value="1"
                    min="1"
                    max="10"
                    required
                    aria-describedby="depth-help" />
-            <span id="depth-help" class="help-text">Maximum request depth to 
search for packages (1-10)</span>
+            <span id="depth-help" class="form-text text-muted">Maximum request 
depth to search for packages (1-10)</span>
           </td>
         </tr>
 
         <tr>
-          <th>
+          <th class="text-end pe-3 align-top">
             <label for="bulk_require_signatures">Require signatures:</label>
           </th>
-          <td>
-            <div class="checkbox-group">
-              <div>
+          <td class="align-top">
+            <div class="mb-2">
+              <div class="form-check my-2">
                 <input type="checkbox"
+                       class="form-check-input"
                        id="bulk_require_signatures"
                        name="require_signatures"
                        checked />
-                <label for="bulk_require_signatures">Only download packages 
that have corresponding .asc signature files</label>
+                <label class="form-check-label" for="bulk_require_signatures">
+                  Only download packages that have corresponding .asc 
signature files
+                </label>
               </div>
             </div>
           </td>
@@ -343,7 +264,9 @@
         <tr>
           <td></td>
           <td>
-            <button type="submit" {% if not releases %}disabled{% endif %}>Add 
packages from URL</button>
+            <button type="submit"
+                    class="btn btn-primary mt-3"
+                    {% if not releases %}disabled{% endif %}>Add packages from 
URL</button>
           </td>
         </tr>
       </tbody>
diff --git a/atr/templates/package-check.html b/atr/templates/package-check.html
index 6e103ff..5cf3e53 100644
--- a/atr/templates/package-check.html
+++ b/atr/templates/package-check.html
@@ -8,233 +8,11 @@
   View the status and results of package verification tasks.
 {% endblock description %}
 
-{% block stylesheets %}
-  {{ super() }}
-  <style>
-      .task-actions {
-          display: flex;
-          gap: 1rem;
-          margin-bottom: 1rem;
-      }
-
-      .task-summary {
-          display: flex;
-          gap: 1rem;
-          align-items: center;
-          padding: 1rem;
-          background: #f8f9fa;
-          border: 1px solid #dee2e6;
-          border-radius: 4px;
-          margin-bottom: 1rem;
-      }
-
-      .summary-title {
-          font-weight: 600;
-          margin-right: 1rem;
-      }
-
-      .summary-stats {
-          display: flex;
-          gap: 1rem;
-          flex-wrap: wrap;
-      }
-
-      .summary-stat {
-          display: flex;
-          align-items: center;
-          gap: 0.5rem;
-          padding: 0.5rem 1rem;
-          border-radius: 4px;
-          font-weight: 500;
-      }
-
-      .summary-stat .count {
-          font-size: 1.2em;
-          /* font-weight: 600; */
-      }
-
-      .summary-stat.completed {
-          background: #d1e7dd;
-          border: 1px solid #a3cfbb;
-      }
-
-      .summary-stat.failed {
-          background: #f8d7da;
-          border: 1px solid #f5c6cb;
-      }
-
-      .summary-stat.active {
-          background: #cff4fc;
-          border: 1px solid #9eeaf9;
-      }
-
-      .summary-stat.queued {
-          background: #f8f9fa;
-          border: 1px solid #dee2e6;
-      }
-
-      .task-actions .button-form {
-          margin: 0;
-      }
-
-      .task-list {
-          margin: 1rem 0;
-      }
-
-      .task-item {
-          border: 2px solid #d1d2d3;
-          border-radius: 4px;
-          padding: 1rem;
-          margin-bottom: 1rem;
-      }
-
-      .task-header {
-          display: flex;
-          justify-content: space-between;
-          align-items: center;
-          margin-bottom: 0.5rem;
-      }
-
-      .task-type {
-          font-weight: 600;
-      }
-
-      .task-status {
-          padding: 0.25rem 0.5rem;
-          border-radius: 4px;
-          font-size: 0.9em;
-      }
-
-      .status-queued {
-          background: #f8f9fa;
-          border: 1px solid #dee2e6;
-      }
-
-      .status-active {
-          background: #cff4fc;
-          border: 1px solid #9eeaf9;
-      }
-
-      .status-completed {
-          background: #d1e7dd;
-          border: 1px solid #a3cfbb;
-      }
-
-      .status-failed {
-          background: #f8d7da;
-          border: 1px solid #f5c6cb;
-      }
-
-      .task-details {
-          margin-top: 0.5rem;
-          font-size: 0.9em;
-      }
-
-      .task-result {
-          margin-top: 0.5rem;
-          padding: 0.5rem;
-          background: #f8f9fa;
-          border-radius: 4px;
-      }
-
-      .task-result details {
-          margin-top: 0.5rem;
-      }
-
-      /* TODO: Move to atr.css */
-      details summary {
-          padding: 0.5rem;
-          cursor: pointer;
-          user-select: none;
-      }
-
-      .task-result summary:hover {
-          background: #e9ecef;
-      }
-
-      .task-result table {
-          margin-top: 0.5rem;
-          width: 100%;
-          border-collapse: collapse;
-      }
-
-      .task-result th,
-      .task-result td {
-          padding: 0.5rem;
-          text-align: left;
-          border: 1px solid #dee2e6;
-      }
-
-      .task-result th {
-          background: #f8f9fa;
-          font-weight: 600;
-      }
-
-      .file-list-table {
-          width: 100%;
-          border-collapse: collapse;
-          margin-top: 0.5rem;
-      }
-
-      .file-list-table th,
-      .file-list-table td {
-          padding: 0.25rem 0.5rem;
-          text-align: left;
-          border: 1px solid #dee2e6;
-          font-size: 0.9em;
-      }
-
-      .file-list-table th {
-          background: #e9ecef;
-          font-weight: 600;
-      }
-
-      .license-summary {
-          display: flex;
-          gap: 1rem;
-          margin-bottom: 0.5rem;
-      }
-
-      .license-stat {
-          padding: 0.25rem 0.5rem;
-          border-radius: 4px;
-          font-size: 0.9em;
-      }
-
-      .license-approved {
-          background: #d1e7dd;
-          border: 1px solid #a3cfbb;
-      }
-
-      .license-unapproved {
-          background: #f8d7da;
-          border: 1px solid #f5c6cb;
-      }
-
-      .license-unknown {
-          background: #fff3cd;
-          border: 1px solid #ffecb5;
-      }
-
-      .package-info {
-          margin-bottom: 2rem;
-          padding: 1rem;
-          background: #f8f9fa;
-          border: 1px solid #dee2e6;
-          border-radius: 4px;
-      }
-
-      .package-info h2 {
-          margin-top: 0;
-      }
-  </style>
-{% endblock stylesheets %}
-
 {% block content %}
   <a href="{{ url_for('root_candidate_review') }}" class="back-link">← Back to 
Release Candidates</a>
 
-  <div class="package-info">
-    <h2>Package details</h2>
+  <div class="p-3 mb-4 bg-light border rounded">
+    <h2 class="mt-0">Package details</h2>
     <p>
       <strong>Filename:</strong> {{ package.filename }}
     </p>
@@ -244,7 +22,7 @@
     <p>
       <strong>Size:</strong> {{ format_file_size(package.bytes_size) }}
     </p>
-    <p>
+    <p class="mb-0">
       <strong>Uploaded:</strong> {{ package.uploaded.strftime("%Y-%m-%d %H:%M 
UTC") }}
     </p>
   </div>
@@ -256,9 +34,9 @@
   <h2>Verification tasks</h2>
 
   {% if tasks %}
-    <div class="task-summary">
-      <span class="summary-title">Status summary:</span>
-      <div class="summary-stats">
+    <div class="d-flex align-items-center p-3 mb-3 bg-light border rounded">
+      <span class="fw-bold me-3">Status summary:</span>
+      <div class="d-flex flex-wrap gap-3">
         {% with %}
           {% set status_counts = {
                       'completed': tasks|selectattr("status.value", "equalto", 
"completed")|list|length,
@@ -269,9 +47,9 @@
 
           {% for status, count in status_counts.items() %}
             {% if count > 0 %}
-              <div class="summary-stat {{ status }}">
-                <span class="count">{{ count }}</span>
-                <span class="label">
+              <div class="d-flex align-items-center gap-2 px-3 py-2 rounded 
fw-medium {% if status == 'completed' %}bg-success-subtle border 
border-success-subtle {% elif status == 'failed' %}bg-danger-subtle border 
border-danger-subtle {% elif status == 'active' %}bg-info-subtle border 
border-info-subtle {% else %}bg-light border{% endif %}">
+                <span class="fs-5">{{ count }}</span>
+                <span>
                   {%- if status == "queued" -%}
                     Pending
                   {%- elif status == "active" -%}
@@ -292,28 +70,28 @@
     </div>
   {% endif %}
 
-  <div class="task-actions">
-    <button type="button" onclick="toggleAllDetails()" 
class="verify-link">Toggle all details</button>
+  <div class="d-flex gap-3 mb-3">
+    <button type="button" onclick="toggleAllDetails()" class="btn 
btn-secondary">Toggle all details</button>
     {% if tasks and all_tasks_completed %}
       <form method="post"
             action="{{ url_for('root_package_check_restart') }}"
-            class="button-form">
+            class="m-0">
         <input type="hidden"
                name="artifact_sha3"
                value="{{ package.artifact_sha3 }}" />
         <input type="hidden" name="release_key" value="{{ release.storage_key 
}}" />
-        <button type="submit" class="verify-link">Restart all checks</button>
+        <button type="submit" class="btn btn-primary">Restart all 
checks</button>
       </form>
     {% endif %}
   </div>
 
-  <div class="task-list">
+  <div class="mb-3">
     {% if tasks %}
       {% for task in tasks %}
-        <div class="task-item">
-          <div class="task-header">
-            <span class="task-type">{{ task.task_type.replace('_', ' 
').replace("verify ", "").title() }}</span>
-            <span class="task-status status-{{ task.status.value.lower() }}">
+        <div class="border border-2 rounded p-3 mb-3">
+          <div class="d-flex justify-content-between align-items-center mb-2">
+            <span class="fw-bold">{{ task.task_type.replace('_', ' 
').replace("verify ", "").title() }}</span>
+            <span class="badge rounded-pill {% if task.status.value == 
'queued' %}bg-secondary {% elif task.status.value == 'active' %}bg-info {% elif 
task.status.value == 'completed' %}bg-success {% elif task.status.value == 
'failed' %}bg-danger {% else %}bg-secondary{% endif %}">
               {%- if task.status.value == "queued" -%}
                 Pending
               {%- elif task.status.value == "active" -%}
@@ -327,60 +105,60 @@
               {%- endif -%}
             </span>
           </div>
-          <div class="task-details">
+          <div class="small">
             <div>Started: {{ task.started.strftime("%Y-%m-%d %H:%M UTC") if 
task.started else "Not started" }}</div>
             <div>Completed: {{ task.completed.strftime("%Y-%m-%d %H:%M UTC") 
if task.completed else "Not completed" }}</div>
             {% if task.result and task.result[0] is mapping and 
task.result|length == 1 %}
-              <details class="task-result">
+              <details class="mt-2 p-2 bg-light rounded">
                 {% if task.error %}
-                  <summary>Issue: {{ task.error }}</summary>
+                  <summary class="cursor-pointer user-select-none p-2">Issue: 
{{ task.error }}</summary>
                 {% else %}
-                  <summary>View detailed results</summary>
+                  <summary class="cursor-pointer user-select-none p-2">View 
detailed results</summary>
                 {% endif %}
 
                 {% if task.task_type == 'verify_rat_license' and 
task.result[0] is mapping %}
-                  <div class="license-summary">
-                    <span class="license-stat license-approved">
+                  <div class="d-flex gap-3 mb-2">
+                    <span class="badge bg-success-subtle text-success-emphasis 
border border-success-subtle px-2 py-1">
                       <strong>{{ task.result[0].approved_licenses }}</strong> 
files with approved licenses
                     </span>
                     {% if task.result[0].unapproved_licenses > 0 %}
-                      <span class="license-stat license-unapproved">
+                      <span class="badge bg-danger-subtle text-danger-emphasis 
border border-danger-subtle px-2 py-1">
                         <strong>{{ task.result[0].unapproved_licenses 
}}</strong> files with unapproved licenses
                       </span>
                     {% endif %}
                     {% if task.result[0].unknown_licenses > 0 %}
-                      <span class="license-stat license-unknown">
+                      <span class="badge bg-warning-subtle 
text-warning-emphasis border border-warning-subtle px-2 py-1">
                         <strong>{{ task.result[0].unknown_licenses }}</strong> 
files with unknown licenses
                       </span>
                     {% endif %}
                   </div>
                 {% endif %}
 
-                <table class="result-table">
+                <table class="table table-bordered mt-2">
                   <tbody>
                     {% for key, value in task.result[0].items() %}
                       {% if key != "debug_info" %}
                         <tr>
-                          <th>{{ key|replace('_', ' ') |title }}</th>
+                          <th class="bg-light fw-bold">{{ key|replace('_', ' 
') |title }}</th>
                           <td>
                             {% if value is boolean %}
                               {{ "Yes" if value else "No" }}
                             {% elif value is mapping %}
-                              <table class="nested-table">
+                              <table class="table table-sm mb-0">
                                 {% for k, v in value.items() %}
                                   <tr>
-                                    <th>{{ k|replace('_', ' ') |title }}</th>
+                                    <th class="bg-light fw-bold">{{ 
k|replace('_', ' ') |title }}</th>
                                     <td>{{ v }}</td>
                                   </tr>
                                 {% endfor %}
                               </table>
                             {% elif key == "unapproved_files" or key == 
"unknown_license_files" %}
                               {% if value|length > 0 %}
-                                <table class="file-list-table">
+                                <table class="table table-sm table-bordered 
mb-0">
                                   <thead>
                                     <tr>
-                                      <th>File</th>
-                                      <th>License</th>
+                                      <th class="bg-light fw-bold">File</th>
+                                      <th class="bg-light fw-bold">License</th>
                                     </tr>
                                   </thead>
                                   <tbody>
@@ -408,10 +186,10 @@
                 </table>
               </details>
             {% else %}
-              {% if task.error %}<div class="task-result">Issue: {{ task.error 
}}</div>{% endif %}
+              {% if task.error %}<div class="mt-2 p-2 bg-light rounded">Issue: 
{{ task.error }}</div>{% endif %}
               {% if task.result %}
-                <div class="task-result">
-                  <pre>{{ task.result }}</pre>
+                <div class="mt-2 p-2 bg-light rounded">
+                  <pre class="mb-0">{{ task.result }}</pre>
                 </div>
               {% endif %}
             {% endif %}
diff --git a/atr/templates/project-directory.html 
b/atr/templates/project-directory.html
index 6c7404f..903940c 100644
--- a/atr/templates/project-directory.html
+++ b/atr/templates/project-directory.html
@@ -8,91 +8,50 @@
   List of all ASF projects and their latest releases.
 {% endblock description %}
 
-{% block head_extra %}
-  <style>
-      .pmc-grid {
-          display: grid;
-          grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
-          gap: 1.5rem;
-          margin: 2rem 0;
-      }
-
-      .pmc-card {
-          background: white;
-          border: 1px solid #e0e0e0;
-          border-radius: 8px;
-          padding: 1.5rem;
-          box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
-          transition: transform 0.2s ease, box-shadow 0.2s ease;
-      }
-
-      .pmc-card:hover {
-          transform: translateY(-2px);
-          box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
-      }
-
-      .pmc-name {
-          font-size: 1.5rem;
-          font-weight: 500;
-          color: #333;
-          margin-bottom: 1rem;
-      }
-
-      .pmc-stats {
-          display: grid;
-          grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
-          gap: 1rem;
-      }
-
-      .stat-item {
-          background: #f8f9fa;
-          padding: 0.75rem;
-          border-radius: 6px;
-          text-align: center;
-      }
-
-      .stat-label {
-          display: block;
-          color: #666;
-          font-size: 0.9rem;
-          margin-bottom: 0.25rem;
-      }
-
-      .stat-value {
-          display: block;
-          font-weight: 500;
-          font-size: 1.25rem;
-          color: #333;
-      }
-  </style>
-{% endblock head_extra %}
-
 {% block content %}
   <h1>Project directory</h1>
   <p class="intro">Current ASF projects and their releases:</p>
 
-  <input type="text"
-         id="pmc-filter"
-         onkeydown="if (event.key == 'Enter'){ filter() }" />
-  <button type="button" onclick="filter()">Filter</button>
+  <div class="mb-3">
+    <input type="text"
+           id="pmc-filter"
+           class="form-control d-inline-block w-auto" />
+    <button type="button" class="btn btn-primary" 
id="filter-button">Filter</button>
+  </div>
 
-  <div class="pmc-grid">
+  <div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
     {% for pmc in projects %}
-      <div class="pmc-card"
-           onclick="location.href='{{ url_for("root_project_view", 
project_name=pmc.name) }}';">
-        <div class="pmc-name">{{ pmc.display_name }}</div>
-        <div class="pmc-stats">
-          <div class="stat-item">
-            <span class="stat-label">PMC Members</span>
-            <span class="stat-value">{{ pmc.pmc_members|length }}</span>
-          </div>
-          <div class="stat-item">
-            <span class="stat-label">Committers</span>
-            <span class="stat-value">{{ pmc.committers|length }}</span>
-          </div>
-          <div class="stat-item">
-            <span class="stat-label">Release Managers</span>
-            <span class="stat-value">{{ pmc.release_managers|length }}</span>
+      <div class="col">
+        <div class="card h-100 shadow-sm cursor-pointer hover-lift 
project-card"
+             data-project-url="{{ url_for('root_project_view', name=pmc.name) 
}}">
+          <div class="card-body">
+            <h2 class="card-title fs-4 mb-3">{{ pmc.display_name }}</h2>
+            <div class="row g-3">
+              <div class="col-4">
+                <div class="card h-100 bg-light border-0">
+                  <div class="card-body p-2 d-flex flex-column 
justify-content-between text-center">
+                    <small class="text-secondary">PMC Members</small>
+                    <span class="fs-4 fw-medium mt-2">{{ 
pmc.pmc_members|length }}</span>
+                  </div>
+                </div>
+              </div>
+              <div class="col-4">
+                <div class="card h-100 bg-light border-0">
+                  <div class="card-body p-2 d-flex flex-column 
justify-content-between text-center">
+                    <small class="text-secondary">Committers</small>
+                    <span class="fs-4 fw-medium mt-2">{{ pmc.committers|length 
}}</span>
+                  </div>
+                </div>
+              </div>
+              <div class="col-4">
+                <div class="card h-100 bg-light border-0">
+                  <div class="card-body p-2 d-flex flex-column 
justify-content-between text-center">
+                    <small class="text-secondary">Release Managers</small>
+                    <span class="fs-4 fw-medium mt-2">{{ 
pmc.release_managers|length }}</span>
+                  </div>
+                </div>
+              </div>
+            </div>
           </div>
         </div>
       </div>
@@ -105,16 +64,32 @@
   <script>
       function filter() {
           const pmcFilter = document.getElementById("pmc-filter").value;
-          const cards = document.getElementsByClassName("pmc-card");
+          const cards = document.querySelectorAll(".project-card");
           for (let card of cards) {
-              const nameElement = card.getElementsByClassName("pmc-name");
-              const name = nameElement[0].innerHTML;
+              const nameElement = card.querySelector(".card-title");
+              const name = nameElement.innerHTML;
               if (!pmcFilter) {
-                  card.hidden = false;
+                  card.parentElement.hidden = false;
               } else {
-                  card.hidden = !name.match(new RegExp(pmcFilter, 'i'));
+                  card.parentElement.hidden = !name.match(new 
RegExp(pmcFilter, 'i'));
               }
           }
       }
+
+      // Add event listeners
+      document.getElementById("filter-button").addEventListener("click", 
filter);
+      document.getElementById("pmc-filter").addEventListener("keydown", 
function(event) {
+          if (event.key === "Enter") {
+              filter();
+              event.preventDefault();
+          }
+      });
+
+      // Add click handlers for project cards
+      document.querySelectorAll(".project-card").forEach(function(card) {
+          card.addEventListener("click", function() {
+              window.location.href = this.getAttribute("data-project-url");
+          });
+      });
   </script>
 {% endblock javascripts %}
diff --git a/atr/templates/project-view.html b/atr/templates/project-view.html
index adfcd58..9945c52 100644
--- a/atr/templates/project-view.html
+++ b/atr/templates/project-view.html
@@ -8,193 +8,116 @@
   Release candidates to which you have access.
 {% endblock description %}
 
-{% block stylesheets %}
-  {{ super() }}
-  <style>
-      .card-header {
-          border: 1px solid #ddd;
-          border-radius: 4px;
-          padding: 1rem;
-          margin-bottom: 1rem;
-          background-color: #f8f8f8;
-      }
-
-      .card-header h3 {
-          margin: 0 0 0.5rem 0;
-      }
-
-      .card-meta {
-          color: #666;
-          font-size: 0.9em;
-          display: flex;
-          flex-wrap: wrap;
-          gap: 1rem;
-          margin-bottom: .5rem;
-          padding-bottom: 1rem;
-          border-bottom: 1px solid #ddd;
-      }
-
-      .card-meta-item::after {
-          content: "•";
-          margin-left: 1rem;
-          color: #ccc;
-      }
-
-      .card-meta-item:last-child::after {
-          content: none;
-      }
-
-      .card-body {
-          display: flex;
-          gap: 1rem;
-          align-items: center;
-      }
-
-      .keys-grid {
-          display: grid;
-          gap: 1.5rem;
-      }
-
-      .key-card {
-          background: white;
-          border: 1px solid #d1d2d3;
-          border-radius: 4px;
-          overflow: hidden;
-          padding: 1rem;
-      }
-
-      .key-card table {
-          margin: 0;
-      }
-
-      .key-card td {
-          word-break: break-all;
-      }
-
-      .key-card h3 {
-          margin-top: 0;
-          margin-bottom: 1rem;
-      }
-
-      .actions {
-          display: flex;
-          gap: 1rem;
-          align-items: center;
-      }
-
-      .add-link {
-          display: inline-block;
-          padding: 0.5rem 1rem;
-          background: #004477;
-          color: white;
-          border: none;
-          border-radius: 4px;
-          cursor: pointer;
-          font-weight: 500;
-          text-decoration: none;
-          font-size: 17px;
-      }
-
-      .add-link:hover {
-          background: #003366;
-          color: white;
-      }
-  </style>
-{% endblock stylesheets %}
-
 {% block content %}
   <h1>{{ project.display_name | capitalize }}</h1>
 
-  <div class="card-header">
-    <h3>Members</h3>
-    <div class="card-meta">
-      <span class="card-meta-item">PMC: {{ project.pmc_members|length }}</span>
-      <span class="card-meta-item">Committers: {{ project.committers|length 
}}</span>
+  <div class="card mb-4">
+    <div class="card-header bg-light">
+      <h3 class="mb-2">Members</h3>
+    </div>
+    <div class="card-body">
+      <div class="d-flex flex-wrap gap-3 small mb-1">
+        <span>PMC: {{ project.pmc_members|length }}</span>
+        <span class="d-flex align-items-center">
+          <span>Committers: {{ project.committers|length }}</span>
+        </span>
+      </div>
     </div>
   </div>
 
-  <div class="card-header">
-    <h3>Signing Keys</h3>
-    <div class="card-meta"></div>
-    <div class="keys-grid">
-      {% for key in project.public_signing_keys %}
-        <div class="key-card">
-          <table>
-            <tbody>
-              <tr>
-                <th>Fingerprint</th>
-                <td>{{ key.fingerprint }}</td>
-              </tr>
-              <tr>
-                <th>Key Type</th>
-                <td>{{ algorithms[key.algorithm] }} ({{ key.length }} 
bits)</td>
-              </tr>
-              <tr>
-                <th>Created</th>
-                <td>{{ key.created.strftime("%Y-%m-%d %H:%M:%S") }}</td>
-              </tr>
-              <tr>
-                <th>Expires</th>
-                <td>
-                  {% if key.expires %}
-                    {% set days_until_expiry = (key.expires - now).days %}
-                    {% if days_until_expiry < 0 %}
-                      <span class="expiry-error">
-                        {{ key.expires.strftime("%Y-%m-%d %H:%M:%S") }}
-                        <span class="notice-badge error">Expired</span>
-                      </span>
-                    {% elif days_until_expiry <= 30 %}
-                      <span class="expiry-warning">
-                        {{ key.expires.strftime("%Y-%m-%d %H:%M:%S") }}
-                        <span class="notice-badge warning">Expires in {{ 
days_until_expiry }} days</span>
-                      </span>
-                    {% else %}
-                      {{ key.expires.strftime("%Y-%m-%d %H:%M:%S") }}
-                    {% endif %}
-                  {% else %}
-                    Never
-                  {% endif %}
-                </td>
-              </tr>
-              <tr>
-                <th>User ID</th>
-                <td>{{ key.declared_uid or 'Not specified' }}</td>
-              </tr>
-            </tbody>
-          </table>
-        </div>
-      {% endfor %}
+  <div class="card mb-4">
+    <div class="card-header bg-light">
+      <h3 class="mb-2">Signing Keys</h3>
+    </div>
+    <div class="card-body">
+      <div class="row row-cols-1 g-4">
+        {% for key in project.public_signing_keys %}
+          <div class="col">
+            <div class="card h-100 border">
+              <div class="card-body">
+                <table class="table mb-0">
+                  <tbody>
+                    <tr>
+                      <th class="border-0">Fingerprint</th>
+                      <td class="text-break border-0">{{ key.fingerprint 
}}</td>
+                    </tr>
+                    <tr>
+                      <th class="border-0">Key Type</th>
+                      <td class="text-break border-0">{{ 
algorithms[key.algorithm] }} ({{ key.length }} bits)</td>
+                    </tr>
+                    <tr>
+                      <th class="border-0">Created</th>
+                      <td class="text-break border-0">{{ 
key.created.strftime("%Y-%m-%d %H:%M:%S") }}</td>
+                    </tr>
+                    <tr>
+                      <th class="border-0">Expires</th>
+                      <td class="text-break border-0">
+                        {% if key.expires %}
+                          {% set days_until_expiry = (key.expires - now).days 
%}
+                          {% if days_until_expiry < 0 %}
+                            <span class="text-danger fw-bold">
+                              {{ key.expires.strftime("%Y-%m-%d %H:%M:%S") }}
+                              <span class="badge bg-danger text-white 
ms-2">Expired</span>
+                            </span>
+                          {% elif days_until_expiry <= 30 %}
+                            <span class="text-warning fw-bold">
+                              {{ key.expires.strftime("%Y-%m-%d %H:%M:%S") }}
+                              <span class="badge bg-warning text-dark 
ms-2">Expires in {{ days_until_expiry }} days</span>
+                            </span>
+                          {% else %}
+                            {{ key.expires.strftime("%Y-%m-%d %H:%M:%S") }}
+                          {% endif %}
+                        {% else %}
+                          Never
+                        {% endif %}
+                      </td>
+                    </tr>
+                    <tr>
+                      <th class="border-0">User ID</th>
+                      <td class="text-break border-0">{{ key.declared_uid or 
'Not specified' }}</td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+            </div>
+          </div>
+        {% endfor %}
+      </div>
     </div>
   </div>
 
-  <div class="card-header">
-    <h3>Voting Policy</h3>
-    <div class="actions">
-      <!-- TODO: This is a PMC, not a project -->
-      <a class="add-link"
-         href="{{ url_for('root_project_voting_policy_add', name=project.name) 
}}"><i class="fa-solid fa-plus"></i></a>
+  <div class="card mb-4">
+    <div class="card-header bg-light d-flex justify-content-between 
align-items-center">
+      <h3 class="mb-0">Voting Policy</h3>
+      <div>
+        <!-- TODO: This is a PMC, not a project -->
+        <a class="btn btn-primary"
+           href="{{ url_for('root_project_voting_policy_add', 
name=project.name) }}"><i class="fa-solid fa-plus"></i></a>
+      </div>
     </div>
-    <!--    <div class="card-meta"></div>-->
     <div class="card-body">
       {% if project.vote_policy %}
         {% set vp = project.vote_policy %}
-        <div class="key-card">
-          <table>
-            <tbody>
-              <tr>
-                <th>Mailto Addresses</th>
-                <td>{{ vp.mailto_addresses }}</td>
-              </tr>
-            </tbody>
-          </table>
+        <div class="card border">
+          <div class="card-body">
+            <table class="table mb-0">
+              <tbody>
+                <tr>
+                  <th class="border-0">Mailto Addresses</th>
+                  <td class="text-break border-0">{{ vp.mailto_addresses 
}}</td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
         </div>
       {% endif %}
     </div>
   </div>
 
-  <div class="card-header">
-    <h3>Product Lines</h3>
-    <div class="card-meta"></div>
+  <div class="card mb-4">
+    <div class="card-header bg-light">
+      <h3 class="mb-0">Product Lines</h3>
+    </div>
     <div class="card-body"></div>
   </div>
 
diff --git a/atr/templates/release-bulk.html b/atr/templates/release-bulk.html
index b969005..c77d858 100644
--- a/atr/templates/release-bulk.html
+++ b/atr/templates/release-bulk.html
@@ -14,166 +14,26 @@
   {% endif %}
 {% endblock head_extra %}
 
-{% block stylesheets %}
-  {{ super() }}
-  <style>
-      nav.breadcrumbs {
-          margin-bottom: 1rem;
-      }
-
-      nav.breadcrumbs a {
-          text-decoration: none;
-      }
-
-      .task-container {
-          margin: 1rem 0;
-      }
-
-      .task-header {
-          display: flex;
-          justify-content: space-between;
-          align-items: center;
-          margin-bottom: 1rem;
-          padding: 1rem;
-          background: #f8f9fa;
-          border: 1px solid #dee2e6;
-          border-radius: 4px;
-      }
-
-      .task-title {
-          font-weight: 600;
-      }
-
-      .task-status {
-          padding: 0.25rem 0.5rem;
-          border-radius: 4px;
-          font-size: 0.9em;
-      }
-
-      .status-queued {
-          background: #f8f9fa;
-          border: 1px solid #dee2e6;
-      }
-
-      .status-active {
-          background: #cff4fc;
-          border: 1px solid #9eeaf9;
-      }
-
-      .status-completed {
-          background: #d1e7dd;
-          border: 1px solid #a3cfbb;
-      }
-
-      .status-failed {
-          background: #f8d7da;
-          border: 1px solid #f5c6cb;
-      }
-
-      .task-details {
-          margin-top: 1rem;
-          padding: 1rem;
-          background: #fff;
-          border: 1px solid #dee2e6;
-          border-radius: 4px;
-      }
-
-      .detail-row {
-          display: flex;
-          margin-bottom: 1rem;
-      }
-
-      .detail-label {
-          width: 150px;
-          font-weight: 600;
-      }
-
-      .progress-container {
-          margin: 1rem 0;
-      }
-
-      /* Progress bar styling */
-      progress {
-          width: 100%;
-          height: 20px;
-          border-radius: 4px;
-          overflow: hidden;
-          border: 1px solid #dee2e6;
-      }
-
-      /* Styling the progress bar for different browsers */
-      progress::-webkit-progress-bar {
-          background-color: #f8f9fa;
-          border-radius: 4px;
-      }
-
-      progress::-webkit-progress-value {
-          background-color: #0366d6;
-          transition: width 0.3s ease;
-      }
-
-      progress::-moz-progress-bar {
-          background-color: #0366d6;
-          border-radius: 4px;
-      }
-
-      .progress-text {
-          margin-top: 0.5rem;
-          font-size: 0.9em;
-          color: #666;
-      }
-
-      .message-box {
-          margin-top: 1rem;
-          margin-bottom: 1rem;
-          padding: 1rem;
-          background: #f8f9fa;
-          border: 1px solid #dee2e6;
-          border-radius: 4px;
-      }
-
-      .error-box {
-          margin-top: 1rem;
-          padding: 1rem;
-          background: #f8d7da;
-          border: 1px solid #f5c6cb;
-          border-radius: 4px;
-          color: #842029;
-      }
-
-      .info-box {
-          margin-top: 1rem;
-          padding: 1rem;
-          background: #cff4fc;
-          border: 1px solid #9eeaf9;
-          border-radius: 4px;
-      }
-
-      .full-width {
-          width: 100%;
-      }
-  </style>
-{% endblock stylesheets %}
-
 {% block content %}
-  <div class="task-container">
-    <nav class="breadcrumbs">
-      <a href="{{ url_for('root_candidate_review') }}">Release candidates</a>
-      {% if release %}
-        <span>→</span>
-        <span>{{ release.pmc.display_name }}</span>
-        <span>→</span>
-        <span>{{ release.product.product_name if release.product else "Unknown 
product" }}</span>
-        <span>→</span>
-        <span>{{ release.version }}</span>
-      {% endif %}
-      <span>→</span>
-      <span>Bulk download status</span>
+  <div class="my-3">
+    <nav class="mb-3">
+      <ol class="breadcrumb">
+        <li class="breadcrumb-item">
+          <a href="{{ url_for('root_candidate_review') }}"
+             class="text-decoration-none">Release candidates</a>
+        </li>
+        {% if release %}
+          <li class="breadcrumb-item">{{ release.pmc.display_name }}</li>
+          <li class="breadcrumb-item">{{ release.product.product_name if 
release.product else "Unknown product" }}</li>
+          <li class="breadcrumb-item">{{ release.version }}</li>
+        {% endif %}
+        <li class="breadcrumb-item active">Bulk download status</li>
+      </ol>
     </nav>
 
-    <div class="task-header">
-      <div class="task-title">Task status</div>
-      <div class="task-status status-{{ task.status.value.lower() }}">
+    <div class="d-flex justify-content-between align-items-center p-3 mb-3 
bg-light border rounded">
+      <div class="fw-medium">Task status</div>
+      <div class="badge rounded-pill {% if task.status.value == 'queued' 
%}bg-secondary {% elif task.status.value == 'active' %}bg-info {% elif 
task.status.value == 'completed' %}bg-success {% elif task.status.value == 
'failed' %}bg-danger {% else %}bg-secondary{% endif %}">
         {%- if task.status.value == "queued" -%}
           Pending
         {%- elif task.status.value == "active" -%}
@@ -188,76 +48,105 @@
       </div>
     </div>
 
-    <div class="task-details">
-      <div class="detail-row">
-        <div class="detail-label">Task ID</div>
-        <div>{{ task.id }}</div>
-      </div>
-
-      <div class="detail-row">
-        <div class="detail-label">Started</div>
-        <div>
-          {% if task.started %}
-            {{ task.started.strftime("%Y-%m-%d %H:%M:%S UTC") }}
-          {% else %}
-            Not started
-          {% endif %}
+    <div class="card mb-3">
+      <div class="card-body">
+        <div class="row mb-3">
+          <div class="col-md-3 fw-medium">Task ID</div>
+          <div class="col-md-9">{{ task.id }}</div>
         </div>
-      </div>
 
-      {% if task.completed %}
-        <div class="detail-row">
-          <div class="detail-label">Completed</div>
-          <div>{{ task.completed.strftime("%Y-%m-%d %H:%M:%S UTC") }}</div>
+        <div class="row mb-3">
+          <div class="col-md-3 fw-medium">Started</div>
+          <div class="col-md-9">
+            {% if task.started %}
+              {{ task.started.strftime("%Y-%m-%d %H:%M:%S UTC") }}
+            {% else %}
+              Not started
+            {% endif %}
+          </div>
         </div>
-      {% endif %}
 
-      {% if task.result %}
-        {% if task.result.progress is defined %}
-          <div class="progress-container">
-            <progress value="{{ task.result.progress }}" max="100"></progress>
-            <div class="progress-text">{{ task.result.progress }}% 
complete</div>
+        {% if task.completed %}
+          <div class="row mb-3">
+            <div class="col-md-3 fw-medium">Completed</div>
+            <div class="col-md-9">{{ task.completed.strftime("%Y-%m-%d 
%H:%M:%S UTC") }}</div>
           </div>
         {% endif %}
 
-        {% if task.result.message %}<div class="message-box">{{ 
task.result.message }}</div>{% endif %}
+        {% if task.result %}
+          {% if task.result.progress is defined %}
+            <div class="mb-3">
+              <div class="progress mb-2 progress-lg">
+                <div class="progress-bar py-2 fs-6"
+                     role="progressbar"
+                     aria-valuenow="{{ task.result.progress }}"
+                     aria-valuemin="0"
+                     aria-valuemax="100"
+                     data-progress="{{ task.result.progress }}">{{ 
task.result.progress }}%</div>
+              </div>
+              <div class="text-muted small">{{ task.result.progress }}% 
complete</div>
+            </div>
+          {% endif %}
 
-        {% if task.status == TaskStatus.COMPLETED %}
-          <div class="detail-row">
-            <div class="detail-label">Summary</div>
-            <div class="full-width">
-              <table>
-                <tbody>
-                  {% if task.result.url %}
-                    <tr>
-                      <th>URL</th>
-                      <td>{{ task.result.url }}</td>
-                    </tr>
-                  {% endif %}
-                  {% if task.result.file_types %}
-                    <tr>
-                      <th>File types</th>
-                      <td>{{ task.result.file_types|join(", ") }}</td>
-                    </tr>
-                  {% endif %}
-                  {% if task.result.files_downloaded %}
-                    <tr>
-                      <th>Files downloaded</th>
-                      <td>{{ task.result.files_downloaded }}</td>
-                    </tr>
-                  {% endif %}
-                </tbody>
-              </table>
+          {% if task.result.message %}<div class="p-3 mb-3 bg-light border 
rounded">{{ task.result.message }}</div>{% endif %}
+
+          {% if task.status == TaskStatus.COMPLETED %}
+            <div class="row mb-3">
+              <div class="col-md-3 fw-medium">Summary</div>
+              <div class="col-md-9">
+                <div class="border">
+                  <table class="table table-bordered mb-0">
+                    <tbody>
+                      {% if task.result.url %}
+                        <tr>
+                          <th class="bg-light">URL</th>
+                          <td>{{ task.result.url }}</td>
+                        </tr>
+                      {% endif %}
+                      {% if task.result.file_types %}
+                        <tr>
+                          <th class="bg-light">File types</th>
+                          <td>{{ task.result.file_types|join(", ") }}</td>
+                        </tr>
+                      {% endif %}
+                      {% if task.result.files_downloaded %}
+                        <tr>
+                          <th class="bg-light">Files downloaded</th>
+                          <td>{{ task.result.files_downloaded }}</td>
+                        </tr>
+                      {% endif %}
+                    </tbody>
+                  </table>
+                </div>
+              </div>
             </div>
-          </div>
+          {% endif %}
         {% endif %}
-      {% endif %}
 
-      {% if task.error %}<div class="error-box">{{ task.error }}</div>{% endif 
%}
+        {% if task.error %}
+          <div class="p-3 mb-3 bg-danger-subtle border border-danger rounded 
text-danger">{{ task.error }}</div>
+        {% endif %}
+      </div>
     </div>
 
     {% if task.status in [TaskStatus.QUEUED, TaskStatus.ACTIVE] %}
-      <div class="info-box">This page will automatically refresh every 2 
seconds to show the latest status.</div>
+      <div class="p-3 mb-3 bg-info-subtle border border-info rounded">
+        This page will automatically refresh every 2 seconds to show the 
latest status.
+      </div>
     {% endif %}
   </div>
 {% endblock content %}
+
+{% block javascripts %}
+  {{ super() }}
+  <script>
+      // Set progress bar width
+      document.addEventListener("DOMContentLoaded", function() {
+          const progressBar = document.querySelector(".progress-bar");
+          if (progressBar) {
+              const progress = progressBar.getAttribute("data-progress");
+              progressBar.style.width = progress + "%";
+          }
+      });
+  </script>
+{% endblock javascripts %}
diff --git a/atr/templates/release-vote.html b/atr/templates/release-vote.html
index f28b178..0ea117e 100644
--- a/atr/templates/release-vote.html
+++ b/atr/templates/release-vote.html
@@ -8,204 +8,106 @@
   Initiate a vote for a release candidate.
 {% endblock description %}
 
-{% block stylesheets %}
-  {{ super() }}
-  <style>
-      .form-group {
-          margin-bottom: 1.5rem;
-      }
-
-      .form-table {
-          width: 100%;
-          margin-bottom: 1.5rem;
-      }
-
-      .form-table th {
-          width: 200px;
-          text-align: right;
-          padding-right: 1rem;
-          vertical-align: top;
-          font-weight: 500;
-      }
-
-      .form-table td {
-          vertical-align: top;
-      }
-
-      .form-table label {
-          border-bottom: none;
-          padding-bottom: 0;
-      }
-
-      .radio-group {
-          display: flex;
-          gap: 1.5rem;
-      }
-
-      .radio-option {
-          display: flex;
-          align-items: center;
-          gap: 0.5rem;
-      }
-
-      .submit-button {
-          padding: 0.5rem 1rem;
-          background: #004477;
-          color: white;
-          border: none;
-          border-radius: 4px;
-          cursor: pointer;
-          font-weight: 500;
-          font-size: 1rem;
-          margin-top: 1rem;
-      }
-
-      .submit-button:hover {
-          background: #003366;
-      }
-
-      .cancel-link {
-          margin-left: 1rem;
-          color: #666;
-          text-decoration: none;
-      }
-
-      .cancel-link:hover {
-          text-decoration: underline;
-      }
-
-      .release-info {
-          border: 1px solid #ddd;
-          border-radius: 4px;
-          padding: 1rem;
-          margin-bottom: 1.5rem;
-          background-color: #f8f8f8;
-      }
-
-      .email-preview {
-          border: 1px solid #ddd;
-          border-radius: 4px;
-          padding: 1rem;
-          margin-top: 1rem;
-          background-color: #fff;
-          font-family: monospace;
-          white-space: pre-wrap;
-          overflow-x: auto;
-      }
-
-      .preview-header {
-          font-weight: 600;
-          margin-bottom: 0.5rem;
-      }
-
-      .warning-text {
-          color: #856404;
-          background-color: #fff3cd;
-          border: 1px solid #ffeeba;
-          border-radius: 4px;
-          padding: 1rem;
-          margin-bottom: 1.5rem;
-      }
-
-      select,
-      input[type="text"] {
-          padding: 0.375rem;
-          border: 1px solid #ced4da;
-          border-radius: 0.25rem;
-          width: 100%;
-          max-width: 600px;
-      }
-  </style>
-{% endblock stylesheets %}
-
 {% block content %}
-  <h1>Start release vote</h1>
-
-  <div class="release-info">
-    <h3>
-      {{ release.pmc.display_name }} - {{ release.product.product_name if 
release.product else "Unknown" }} {{ release.version }}
-    </h3>
-    <p>Initiating a vote for this release candidate will prepare an email to 
be sent to the appropriate mailing list.</p>
-  </div>
+  <div class="my-4">
+    <h1 class="mb-4">Start release vote</h1>
+
+    <div class="px-3 pb-4 mb-4 bg-light border rounded">
+      <h2 class="mt-4 mb-3 fs-5 border-0">
+        {{ release.pmc.display_name }} - {{ release.product.product_name if 
release.product else "Unknown" }} {{ release.version }}
+      </h2>
+      <p class="mb-0">
+        Initiating a vote for this release candidate will prepare an email to 
be sent to the appropriate mailing list.
+      </p>
+    </div>
 
-  <div class="warning-text">
-    <strong>Note:</strong> This feature is currently in development. The form 
below only sends email to a test account.
-  </div>
+    <div class="p-3 mb-4 bg-warning-subtle border border-warning rounded">
+      <strong>Note:</strong> This feature is currently in development. The 
form below only sends email to a test account.
+    </div>
 
-  <form method="post"
-        action="{{ url_for('root_release_vote') }}"
-        class="striking">
-    <input type="hidden" name="release_key" value="{{ release.storage_key }}" 
/>
+    <form method="post"
+          class="striking py-4 px-5"
+          action="{{ url_for('root_release_vote') }}">
+      <input type="hidden" name="release_key" value="{{ release.storage_key 
}}" />
 
-    <table class="form-table">
-      <tbody>
-        <tr>
-          <th>
+      <div class="mb-4">
+        <div class="row mb-3 pb-3 border-bottom">
+          <div class="col-md-3 text-md-end fw-medium pt-2">
             <label>Send vote email to:</label>
-          </th>
-          <td>
-            <div class="radio-group">
-              <div class="radio-option">
-                <input type="radio" id="list_dev" name="mailing_list" 
value="dev" checked />
-                <label for="list_dev">dev@{{ release.pmc.name 
}}.apache.org</label>
+          </div>
+          <div class="col-md-9">
+            <div class="d-flex gap-4">
+              <div class="form-check">
+                <input type="radio"
+                       class="form-check-input"
+                       id="list_dev"
+                       name="mailing_list"
+                       value="dev"
+                       checked />
+                <label class="form-check-label" for="list_dev">dev@{{ 
release.pmc.name }}.apache.org</label>
               </div>
-              <div class="radio-option">
-                <input type="radio" id="list_private" name="mailing_list" 
value="private" />
-                <label for="list_private">private@{{ release.pmc.name 
}}.apache.org</label>
+              <div class="form-check">
+                <input type="radio"
+                       class="form-check-input"
+                       id="list_private"
+                       name="mailing_list"
+                       value="private" />
+                <label class="form-check-label" for="list_private">private@{{ 
release.pmc.name }}.apache.org</label>
               </div>
             </div>
-          </td>
-        </tr>
+          </div>
+        </div>
 
-        <tr>
-          <th>
+        <div class="row mb-3 pb-3 border-bottom">
+          <div class="col-md-3 text-md-end fw-medium pt-2">
             <label for="vote_duration">Vote duration:</label>
-          </th>
-          <td>
-            <select id="vote_duration" name="vote_duration" 
class="form-select">
+          </div>
+          <div class="col-md-9">
+            <select id="vote_duration" name="vote_duration" class="form-select 
w-75">
               <option value="72">72 hours (minimum)</option>
               <option value="120">5 days</option>
               <option value="168">7 days</option>
             </select>
-          </td>
-        </tr>
+          </div>
+        </div>
 
-        <tr>
-          <th>
+        <div class="row mb-3 pb-3 border-bottom">
+          <div class="col-md-3 text-md-end fw-medium pt-2">
             <label for="gpg_key_id">Your GPG key ID:</label>
-          </th>
-          <td>
+          </div>
+          <div class="col-md-9">
             <input type="text"
                    id="gpg_key_id"
                    name="gpg_key_id"
-                   class="form-control"
+                   class="form-control w-75"
                    placeholder="e.g., 0x1A2B3C4D" />
-          </td>
-        </tr>
+          </div>
+        </div>
 
-        <tr>
-          <th>
+        <div class="row mb-3 pb-3 border-bottom">
+          <div class="col-md-3 text-md-end fw-medium pt-2">
             <label for="commit_hash">Commit hash:</label>
-          </th>
-          <td>
+          </div>
+          <div class="col-md-9">
             <input type="text"
                    id="commit_hash"
                    name="commit_hash"
-                   class="form-control"
+                   class="form-control w-75"
                    placeholder="Git commit hash used for this release" />
-          </td>
-        </tr>
-      </tbody>
-    </table>
-
-    <div class="form-group">
-      <label class="preview-header">Email Preview:</label>
-      <div class="email-preview">{{ email_preview }}</div>
-    </div>
-
-    <div class="form-actions">
-      <button type="submit" class="submit-button">Prepare Vote Email</button>
-      <a href="{{ url_for('root_candidate_review') }}" 
class="cancel-link">Cancel</a>
-    </div>
-  </form>
+          </div>
+        </div>
+      </div>
+
+      <div class="mb-4">
+        <label class="fw-medium mb-2">Email Preview:</label>
+        <pre class="p-3 bg-white border rounded font-monospace 
overflow-auto">{{ email_preview }}</pre>
+      </div>
+
+      <div class="mt-4">
+        <button type="submit" class="btn btn-primary">Prepare Vote 
Email</button>
+        <a href="{{ url_for('root_candidate_review') }}"
+           class="btn btn-link text-secondary">Cancel</a>
+      </div>
+    </form>
+  </div>
 {% endblock content %}
diff --git a/atr/templates/vote-policy-add.html 
b/atr/templates/vote-policy-add.html
index eff43f7..a2aa369 100644
--- a/atr/templates/vote-policy-add.html
+++ b/atr/templates/vote-policy-add.html
@@ -8,15 +8,6 @@
   Create a new vote policy.
 {% endblock description %}
 
-{% block theme_css %}
-  <link rel="stylesheet"
-        href="{{ url_for('static', filename='css/atr-small.css') }}" />
-{% endblock theme_css %}
-
-{% block stylesheets %}
-  {{ super() }}
-{% endblock stylesheets %}
-
 {% block content %}
   <h1>Add vote policy</h1>
   <p>
diff --git a/atr/templates/vote-policy-edit.html 
b/atr/templates/vote-policy-edit.html
index 4977714..0e66997 100644
--- a/atr/templates/vote-policy-edit.html
+++ b/atr/templates/vote-policy-edit.html
@@ -8,15 +8,6 @@
   Edit a vote policy.
 {% endblock description %}
 
-{% block theme_css %}
-  <link rel="stylesheet"
-        href="{{ url_for('static', filename='css/atr-small.css') }}" />
-{% endblock theme_css %}
-
-{% block stylesheets %}
-  {{ super() }}
-{% endblock stylesheets %}
-
 {% block content %}
   <h1>Edit vote policy</h1>
   <p>
diff --git a/bootstrap/custom.scss b/bootstrap/custom.scss
index 369be6d..5c076f4 100644
--- a/bootstrap/custom.scss
+++ b/bootstrap/custom.scss
@@ -6,6 +6,8 @@ $font-family-sans-serif: "Inter", sans-serif;
 $headings-font-family: $font-family-sans-serif;
 $table-cell-padding-y: 0.75rem;
 $table-cell-padding-x: 0.75rem;
+$table-bg: transparent;
+$table-accent-bg: transparent;
 
 // Variables
 @import "../node_modules/bootstrap/scss/variables";
@@ -62,7 +64,7 @@ table.atr-data th {
 }
 
 th {
-  color: #555;
+  color: $dark;
   font-weight: 525;
   @extend .align-middle;
   @extend .w-25;


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to