Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 18 additions & 17 deletions frontend/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="format-detection" content="telephone=no">
<!-- Content Security Policy: Restrict resource loading to prevent XSS.
frame-ancestors is intentionally omitted here because browsers ignore
it in <meta>; it is delivered via the response header from
Expand Down Expand Up @@ -348,7 +349,7 @@ <h2>Global Configuration</h2>
<span class="info-icon">&#9432;<span class="tooltip-text">Email address for purchase notifications and approval requests.</span></span>
</div>
<div class="setting-input">
<input type="email" id="setting-notification-email" placeholder="admin@example.com" required aria-label="Notification email address">
<input type="email" id="setting-notification-email" placeholder="admin@example.com" required aria-label="Notification email address" autocomplete="email">
</div>
</div>
<div class="setting-row">
Expand Down Expand Up @@ -386,7 +387,7 @@ <h2>Global Configuration</h2>
<span class="info-icon">&#9432;<span class="tooltip-text">How many days before a scheduled purchase to send email notification.</span></span>
</div>
<div class="setting-input">
<input type="number" id="setting-notification-days" value="3" min="1" max="30">
<input type="number" id="setting-notification-days" value="3" min="1" max="30" inputmode="numeric" pattern="[0-9]*">
</div>
</div>
<div class="setting-row">
Expand All @@ -395,7 +396,7 @@ <h2>Global Configuration</h2>
<span class="info-icon">&#9432;<span class="tooltip-text">Age (in hours) at which the recommendations cache is considered stale and a background refresh fires automatically. Set to 0 to disable automatic background refresh (the cron scheduler and manual Refresh button still work). Valid range: 0&ndash;8760 (up to one year). Default: 24.</span></span>
</div>
<div class="setting-input">
<input type="number" id="setting-recs-stale-hours" value="24" min="0" max="8760" step="1">
<input type="number" id="setting-recs-stale-hours" value="24" min="0" max="8760" step="1" inputmode="numeric" pattern="[0-9]*">
</div>
</div>
</fieldset>
Expand Down Expand Up @@ -444,7 +445,7 @@ <h2>Purchasing Settings</h2>
<label for="setting-default-coverage">Target Coverage (%)</label>
</div>
<div class="setting-input">
<input type="number" id="setting-default-coverage" value="80" min="0" max="100">
<input type="number" id="setting-default-coverage" value="80" min="0" max="100" inputmode="numeric" pattern="[0-9]*">
</div>
</div>
</fieldset>
Expand Down Expand Up @@ -475,23 +476,23 @@ <h2>Purchasing Settings</h2>
<label for="setting-grace-aws"><span class="provider-badge aws">AWS</span> grace period (days)</label>
</div>
<div class="setting-input">
<input type="number" id="setting-grace-aws" value="7" min="0" max="30" step="1">
<input type="number" id="setting-grace-aws" value="7" min="0" max="30" step="1" inputmode="numeric" pattern="[0-9]*">
</div>
</div>
<div class="setting-row">
<div class="setting-info">
<label for="setting-grace-azure"><span class="provider-badge azure">Azure</span> grace period (days)</label>
</div>
<div class="setting-input">
<input type="number" id="setting-grace-azure" value="7" min="0" max="30" step="1">
<input type="number" id="setting-grace-azure" value="7" min="0" max="30" step="1" inputmode="numeric" pattern="[0-9]*">
</div>
</div>
<div class="setting-row">
<div class="setting-info">
<label for="setting-grace-gcp"><span class="provider-badge gcp">GCP</span> grace period (days)</label>
</div>
<div class="setting-input">
<input type="number" id="setting-grace-gcp" value="7" min="0" max="30" step="1">
<input type="number" id="setting-grace-gcp" value="7" min="0" max="30" step="1" inputmode="numeric" pattern="[0-9]*">
</div>
</div>
</fieldset>
Expand Down Expand Up @@ -539,31 +540,31 @@ <h5>Compute Savings Plans</h5>
<p class="service-default-hint">EC2, Fargate, Lambda — most flexible</p>
<label>Term: <select id="aws-savings-plans-compute-term"><option value="1">1 Year</option><option value="3" selected>3 Years</option></select></label>
<label>Payment: <select id="aws-savings-plans-compute-payment"><option value="no-upfront">No Upfront</option><option value="partial-upfront">Partial</option><option value="all-upfront" selected>All Upfront</option></select></label>
<label>Coverage %: <input type="number" id="aws-savings-plans-compute-coverage" min="0" max="100" value="80"></label>
<label>Coverage %: <input type="number" id="aws-savings-plans-compute-coverage" min="0" max="100" value="80" inputmode="numeric" pattern="[0-9]*"></label>
<label class="toggle-label"><input type="checkbox" id="aws-savings-plans-compute-enabled" checked> Enabled</label>
</div>
<div class="service-default-card">
<h5>EC2 Instance Savings Plans</h5>
<p class="service-default-hint">EC2 only, region-locked — deepest discount</p>
<label>Term: <select id="aws-savings-plans-ec2instance-term"><option value="1">1 Year</option><option value="3" selected>3 Years</option></select></label>
<label>Payment: <select id="aws-savings-plans-ec2instance-payment"><option value="no-upfront">No Upfront</option><option value="partial-upfront">Partial</option><option value="all-upfront" selected>All Upfront</option></select></label>
<label>Coverage %: <input type="number" id="aws-savings-plans-ec2instance-coverage" min="0" max="100" value="80"></label>
<label>Coverage %: <input type="number" id="aws-savings-plans-ec2instance-coverage" min="0" max="100" value="80" inputmode="numeric" pattern="[0-9]*"></label>
<label class="toggle-label"><input type="checkbox" id="aws-savings-plans-ec2instance-enabled" checked> Enabled</label>
</div>
<div class="service-default-card">
<h5>SageMaker Savings Plans</h5>
<p class="service-default-hint">SageMaker training/inference</p>
<label>Term: <select id="aws-savings-plans-sagemaker-term"><option value="1">1 Year</option><option value="3" selected>3 Years</option></select></label>
<label>Payment: <select id="aws-savings-plans-sagemaker-payment"><option value="no-upfront">No Upfront</option><option value="partial-upfront">Partial</option><option value="all-upfront" selected>All Upfront</option></select></label>
<label>Coverage %: <input type="number" id="aws-savings-plans-sagemaker-coverage" min="0" max="100" value="80"></label>
<label>Coverage %: <input type="number" id="aws-savings-plans-sagemaker-coverage" min="0" max="100" value="80" inputmode="numeric" pattern="[0-9]*"></label>
<label class="toggle-label"><input type="checkbox" id="aws-savings-plans-sagemaker-enabled" checked> Enabled</label>
</div>
<div class="service-default-card">
<h5>Database Savings Plans</h5>
<p class="service-default-hint">Reserved for future AWS Database Savings Plans (currently not GA; defaults stored for forward-compatibility)</p>
<label>Term: <select id="aws-savings-plans-database-term"><option value="1">1 Year</option><option value="3" selected>3 Years</option></select></label>
<label>Payment: <select id="aws-savings-plans-database-payment"><option value="no-upfront">No Upfront</option><option value="partial-upfront">Partial</option><option value="all-upfront" selected>All Upfront</option></select></label>
<label>Coverage %: <input type="number" id="aws-savings-plans-database-coverage" min="0" max="100" value="80"></label>
<label>Coverage %: <input type="number" id="aws-savings-plans-database-coverage" min="0" max="100" value="80" inputmode="numeric" pattern="[0-9]*"></label>
<label class="toggle-label"><input type="checkbox" id="aws-savings-plans-database-enabled" checked> Enabled</label>
</div>
</div>
Expand Down Expand Up @@ -836,7 +837,7 @@ <h3>Purchase Configuration</h3>
</select>
</label>
</div>
<label>Target Coverage (%): <input type="number" id="plan-coverage" value="80" min="0" max="100"></label>
<label>Target Coverage (%): <input type="number" id="plan-coverage" value="80" min="0" max="100" inputmode="numeric" pattern="[0-9]*"></label>
</div>

<div class="form-section">
Expand Down Expand Up @@ -866,8 +867,8 @@ <h3>Ramp-up Schedule</h3>
</div>
<div id="custom-ramp-config" class="hidden">
<div class="form-row">
<label>Coverage Per Step (%): <input type="number" id="ramp-step-percent" value="20" min="1" max="100"></label>
<label>Interval (Days): <input type="number" id="ramp-interval-days" value="7" min="1" max="365"></label>
<label>Coverage Per Step (%): <input type="number" id="ramp-step-percent" value="20" min="1" max="100" inputmode="numeric" pattern="[0-9]*"></label>
<label>Interval (Days): <input type="number" id="ramp-interval-days" value="7" min="1" max="365" inputmode="numeric" pattern="[0-9]*"></label>
</div>
</div>
</div>
Expand All @@ -892,7 +893,7 @@ <h3>Automation Settings</h3>
<span class="info-icon">&#9432;<span class="tooltip-text">Send email notification this many days before each scheduled purchase.</span></span>
</div>
<div class="setting-input">
<input type="number" id="plan-notify-days" value="3" min="1" max="30">
<input type="number" id="plan-notify-days" value="3" min="1" max="30" inputmode="numeric" pattern="[0-9]*">
</div>
</div>
<div class="setting-row">
Expand Down Expand Up @@ -1142,7 +1143,7 @@ <h2 id="override-modal-title">Add service override</h2>
</select>
</label>
<label>Coverage (%):
<input type="number" id="override-coverage" min="0" max="100" step="1" placeholder="Inherit (use global default)">
<input type="number" id="override-coverage" min="0" max="100" step="1" placeholder="Inherit (use global default)" inputmode="numeric" pattern="[0-9]*">
</label>

<p class="help-text" id="override-form-error" role="alert"></p>
Expand Down
68 changes: 66 additions & 2 deletions frontend/src/styles/responsive.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,70 @@
/* Responsive styles: Media queries */

/* ---------------------------------------------------------------------------
* Mobile foundations
* ------------------------------------------------------------------------- */

/* Tap-highlight: remove the default iOS/Android blue flash on touch; each
* interactive element supplies its own :active style instead so the tap
* feedback is intentional rather than accidental. */
button,
a,
[role="button"] {
-webkit-tap-highlight-color: transparent;
}

button:active,
a:active,
[role="button"]:active {
opacity: 0.85;
}

/* Safe-area insets: protect content from the iOS notch (top) and home
* indicator (bottom). The fixed topbar sits at top:0 so it needs the top
* inset; the main scrollable area needs bottom padding so the last row of
* content is not obscured by the home bar. */
.app-topbar {
padding-top: max(0px, env(safe-area-inset-top));
}

.app-shell {
padding-bottom: env(safe-area-inset-bottom);
}

/* Touch-target minimums: apply only on coarse-pointer (touch) devices so
* the compact desktop layout is unaffected. 44x44px matches Apple HIG and
* WCAG 2.5.8 guidance for minimum touch-target size. */
@media (pointer: coarse) {
.btn,
button,
[role="button"],
input[type="submit"],
input[type="button"],
input[type="reset"],
select,
input[type="checkbox"],
input[type="radio"] {
min-height: 44px;
min-width: 44px;
}

/* Checkboxes and radio buttons are small by design; grow them via
* padding on the wrapping label so the hit area expands without
* visually bloating the glyph. */
input[type="checkbox"],
input[type="radio"] {
min-height: unset;
min-width: unset;
}

label:has(> input[type="checkbox"]),
label:has(> input[type="radio"]) {
min-height: 44px;
display: flex;
align-items: center;
}
}

/* ---------------------------------------------------------------------------
* Mobile navigation drawer (below 768px)
*
Expand All @@ -11,7 +76,7 @@
* - aria-expanded / aria-hidden toggled from app.ts:setupMobileNav().
* ------------------------------------------------------------------------- */

/* Hamburger button — mobile only */
/* Hamburger button - mobile only */
.hamburger {
display: none; /* hidden above breakpoint */
background: transparent;
Expand Down Expand Up @@ -47,7 +112,6 @@
body.sidebar-open {
overflow: hidden;
}

@media (max-width: 768px) {
header {
flex-direction: column;
Expand Down
Loading