β±οΈ Time Estimate Display System¶
Purpose¶
Board directors and executives need to know exactly how long tasks will take. GetCimple's time estimate system provides:
- Clear time commitments upfront
- Role-specific estimates (board vs management)
- Real-time countdown during tasks
- Historical accuracy tracking
Time Estimate Categories¶
Board Tasks (2-5 minutes)¶
- Strategic decisions
- Risk appetite settings
- Target approvals
- Compliance sign-offs
Management Tasks (15-30 minutes)¶
- Detailed assessments
- Evidence uploads
- Process documentation
- Implementation planning
IT/Technical Tasks (30-60 minutes)¶
- Technical configurations
- Security assessments
- System integrations
- Detailed implementations
Component Specifications¶
Basic Time Estimate Badge¶
<div
class="gc-time-estimate"
role="status"
aria-label="Estimated time: 3 minutes"
>
<svg class="gc-time-estimate__icon" aria-hidden="true">
<use href="#icon-clock"></use>
</svg>
<span class="gc-time-estimate__value">3</span>
<span class="gc-time-estimate__unit">min</span>
</div>
.gc-time-estimate {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 6px 12px;
background: var(--gc-bg-time);
border-radius: 20px;
font-size: 14px;
font-weight: 500;
}
.gc-time-estimate__icon {
width: 16px;
height: 16px;
color: var(--gc-text-secondary);
}
.gc-time-estimate__value {
font-variant-numeric: tabular-nums;
font-weight: 600;
color: var(--gc-text-primary);
}
.gc-time-estimate__unit {
color: var(--gc-text-secondary);
}
/* Role-based coloring */
.gc-time-estimate--board {
background: #eff6ff; /* Light blue */
color: #1e40af;
}
.gc-time-estimate--management {
background: #fef3c7; /* Light amber */
color: #92400e;
}
.gc-time-estimate--technical {
background: #f3f4f6; /* Light gray */
color: #374151;
}
Detailed Time Breakdown¶
<div class="gc-time-breakdown">
<h3 class="gc-time-breakdown__title">Time Investment</h3>
<div class="gc-time-breakdown__total">
<span class="gc-time-breakdown__total-value">8</span>
<span class="gc-time-breakdown__total-unit">minutes total</span>
</div>
<ul class="gc-time-breakdown__list">
<li class="gc-time-breakdown__item gc-time-breakdown__item--board">
<span class="gc-time-breakdown__role">Board Review</span>
<span class="gc-time-breakdown__time">3 min</span>
<span class="gc-time-breakdown__status">Required</span>
</li>
<li class="gc-time-breakdown__item gc-time-breakdown__item--management">
<span class="gc-time-breakdown__role">Management Input</span>
<span class="gc-time-breakdown__time">5 min</span>
<span class="gc-time-breakdown__status">Completed</span>
</li>
</ul>
</div>
Active Timer Component¶
<div class="gc-timer" data-duration="180" role="timer" aria-live="polite">
<div class="gc-timer__display">
<span class="gc-timer__minutes">2</span>
<span class="gc-timer__separator">:</span>
<span class="gc-timer__seconds">47</span>
</div>
<div
class="gc-timer__progress"
role="progressbar"
aria-valuenow="33"
aria-valuemin="0"
aria-valuemax="180"
>
<div class="gc-timer__progress-bar" style="width: 18.3%"></div>
</div>
<div class="gc-timer__controls">
<button class="gc-timer__pause" aria-label="Pause timer">
<svg><use href="#icon-pause"></use></svg>
</button>
<button class="gc-timer__reset" aria-label="Reset timer">
<svg><use href="#icon-reset"></use></svg>
</button>
</div>
</div>
class Timer {
constructor(element) {
this.element = element
this.duration = parseInt(element.dataset.duration)
this.remaining = this.duration
this.isRunning = false
this.interval = null
this.minutesEl = element.querySelector('.gc-timer__minutes')
this.secondsEl = element.querySelector('.gc-timer__seconds')
this.progressBar = element.querySelector('.gc-timer__progress-bar')
this.init()
}
init() {
this.render()
this.bindEvents()
this.start()
}
start() {
if (this.isRunning) return
this.isRunning = true
this.interval = setInterval(() => this.tick(), 1000)
this.element.classList.add('gc-timer--running')
}
pause() {
this.isRunning = false
clearInterval(this.interval)
this.element.classList.remove('gc-timer--running')
}
reset() {
this.pause()
this.remaining = this.duration
this.render()
}
tick() {
if (this.remaining > 0) {
this.remaining--
this.render()
// Warnings at thresholds
if (this.remaining === 60) {
this.element.classList.add('gc-timer--warning')
this.announce('1 minute remaining')
} else if (this.remaining === 0) {
this.complete()
}
}
}
render() {
const minutes = Math.floor(this.remaining / 60)
const seconds = this.remaining % 60
this.minutesEl.textContent = minutes
this.secondsEl.textContent = seconds.toString().padStart(2, '0')
const progress = ((this.duration - this.remaining) / this.duration) * 100
this.progressBar.style.width = `${progress}%`
// Update ARIA
this.element.setAttribute('aria-valuenow', this.duration - this.remaining)
}
complete() {
this.pause()
this.element.classList.add('gc-timer--complete')
this.announce('Time complete')
this.onComplete?.()
}
announce(message) {
// Screen reader announcement
const announcement = document.createElement('div')
announcement.className = 'sr-only'
announcement.setAttribute('role', 'status')
announcement.textContent = message
document.body.appendChild(announcement)
setTimeout(() => announcement.remove(), 1000)
}
bindEvents() {
this.element
.querySelector('.gc-timer__pause')
?.addEventListener('click', () => {
this.isRunning ? this.pause() : this.start()
})
this.element
.querySelector('.gc-timer__reset')
?.addEventListener('click', () => this.reset())
}
}
Time Estimate Cards¶
<!-- Quick Task Card -->
<article class="gc-task-card gc-task-card--quick">
<header class="gc-task-card__header">
<h3 class="gc-task-card__title">Review Compliance Status</h3>
<div class="gc-time-estimate gc-time-estimate--board">
<svg class="gc-time-estimate__icon">
<use href="#icon-lightning"></use>
</svg>
<span>Quick: 2 min</span>
</div>
</header>
<div class="gc-task-card__body">
<p class="gc-task-card__description">
Review quarterly compliance dashboard and approve continued approach
</p>
<div class="gc-task-card__meta">
<span class="gc-task-card__role">Board Decision</span>
<span class="gc-task-card__frequency">Quarterly</span>
</div>
</div>
<footer class="gc-task-card__footer">
<button class="gc-task-card__action">
Start Review
<span class="gc-task-card__time-hint">(2 min)</span>
</button>
</footer>
</article>
<!-- Detailed Task Card -->
<article class="gc-task-card gc-task-card--detailed">
<header class="gc-task-card__header">
<h3 class="gc-task-card__title">Complete E8 Assessment</h3>
<div class="gc-time-breakdown gc-time-breakdown--inline">
<span class="gc-time-breakdown__segment"> IT: 15 min </span>
<span class="gc-time-breakdown__segment"> Board: 3 min </span>
</div>
</header>
<div class="gc-task-card__progress">
<div class="gc-task-card__progress-header">
<span>IT Team Progress</span>
<span>90% Complete</span>
</div>
<div class="gc-task-card__progress-bar">
<div class="gc-task-card__progress-fill" style="width: 90%"></div>
</div>
<p class="gc-task-card__progress-note">Board input needed to finalize</p>
</div>
<footer class="gc-task-card__footer">
<button class="gc-task-card__action">
Complete Board Portion
<span class="gc-task-card__time-hint">(3 min)</span>
</button>
</footer>
</article>
Historical Accuracy Display¶
<div class="gc-time-accuracy">
<h4 class="gc-time-accuracy__title">Time Estimate Accuracy</h4>
<div class="gc-time-accuracy__stats">
<div class="gc-time-accuracy__stat">
<span class="gc-time-accuracy__value">95%</span>
<span class="gc-time-accuracy__label">On Time</span>
</div>
<div class="gc-time-accuracy__stat">
<span class="gc-time-accuracy__value">2.8 min</span>
<span class="gc-time-accuracy__label">Avg Board Time</span>
</div>
<div class="gc-time-accuracy__stat">
<span class="gc-time-accuracy__value">18 min</span>
<span class="gc-time-accuracy__label">Avg Mgmt Time</span>
</div>
</div>
<div class="gc-time-accuracy__chart">
<!-- Sparkline or small chart showing trend -->
</div>
</div>
Visual States¶
Timer States¶
/* Default/Idle */
.gc-timer {
background: var(--gc-bg-card);
border: 2px solid var(--gc-border-default);
}
/* Running */
.gc-timer--running {
border-color: var(--gc-info);
animation: pulse-border 2s infinite;
}
/* Warning (< 1 minute) */
.gc-timer--warning {
border-color: var(--gc-status-warning);
background: #fef3c7;
}
.gc-timer--warning .gc-timer__display {
color: var(--gc-status-warning);
animation: blink 1s infinite;
}
/* Complete */
.gc-timer--complete {
border-color: var(--gc-status-good);
background: #d1fae5;
}
/* Overtime */
.gc-timer--overtime {
border-color: var(--gc-status-critical);
background: #fee2e2;
}
@keyframes pulse-border {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.6;
}
}
@keyframes blink {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
Time Badge Variants¶
/* Ultra-quick (< 1 min) */
.gc-time-estimate--instant {
background: #d1fae5;
color: #065f46;
}
.gc-time-estimate--instant::before {
content: 'β‘';
margin-right: 4px;
}
/* Quick (1-5 min) */
.gc-time-estimate--quick {
background: #dbeafe;
color: #1e40af;
}
/* Moderate (5-15 min) */
.gc-time-estimate--moderate {
background: #fef3c7;
color: #92400e;
}
/* Extended (15-30 min) */
.gc-time-estimate--extended {
background: #f3f4f6;
color: #374151;
}
/* Long (30+ min) */
.gc-time-estimate--long {
background: #fee2e2;
color: #991b1b;
}
Smart Time Features¶
Adaptive Estimates¶
class AdaptiveTimeEstimate {
constructor(taskType, userRole) {
this.taskType = taskType
this.userRole = userRole
this.history = this.loadHistory()
}
getEstimate() {
const baseTime = this.getBaseTime()
const adjustment = this.getAdjustment()
return {
estimate: baseTime + adjustment,
confidence: this.getConfidence(),
range: this.getRange(),
}
}
getBaseTime() {
// Base times by task type and role
const times = {
'board-decision': { board: 3, executive: 5 },
'compliance-review': { board: 5, executive: 15 },
assessment: { board: 2, management: 20, technical: 45 },
}
return times[this.taskType]?.[this.userRole] || 10
}
getAdjustment() {
// Adjust based on user's historical performance
if (this.history.length < 5) return 0
const avgTime =
this.history.reduce((sum, h) => sum + h.actual, 0) / this.history.length
const avgEstimate =
this.history.reduce((sum, h) => sum + h.estimate, 0) / this.history.length
return avgTime - avgEstimate
}
getConfidence() {
// Higher confidence with more history
if (this.history.length >= 20) return 'high'
if (this.history.length >= 10) return 'medium'
return 'low'
}
getRange() {
// Provide a time range based on confidence
const estimate = this.getEstimate().estimate
const variance = this.getConfidence() === 'high' ? 0.1 : 0.25
return {
min: Math.floor(estimate * (1 - variance)),
max: Math.ceil(estimate * (1 + variance)),
}
}
}
Batch Time Calculation¶
class BatchTimeCalculator {
constructor(tasks) {
this.tasks = tasks
}
calculate() {
const grouped = this.groupByRole()
const times = {}
for (const [role, tasks] of Object.entries(grouped)) {
times[role] = {
sequential: this.getSequentialTime(tasks),
batched: this.getBatchedTime(tasks),
savings: 0,
}
times[role].savings = times[role].sequential - times[role].batched
}
return times
}
groupByRole() {
return this.tasks.reduce((groups, task) => {
const role = task.assignedRole
groups[role] = groups[role] || []
groups[role].push(task)
return groups
}, {})
}
getSequentialTime(tasks) {
return tasks.reduce((total, task) => total + task.estimatedTime, 0)
}
getBatchedTime(tasks) {
// Context switching overhead removed when batching
const setupTime = 2 // Fixed setup time
const perTaskTime = tasks.reduce(
(total, task) => total + task.estimatedTime * 0.8,
0
) // 20% efficiency gain
return setupTime + perTaskTime
}
}
Implementation Guidelines¶
React Components¶
// TimeEstimate Component
export const TimeEstimate: React.FC<{
minutes: number
role?: 'board' | 'management' | 'technical'
showIcon?: boolean
}> = ({ minutes, role = 'board', showIcon = true }) => {
const getCategory = () => {
if (minutes <= 1) return 'instant'
if (minutes <= 5) return 'quick'
if (minutes <= 15) return 'moderate'
if (minutes <= 30) return 'extended'
return 'long'
}
return (
<div
className={`gc-time-estimate gc-time-estimate--${role} gc-time-estimate--${getCategory()}`}
>
{showIcon && <ClockIcon />}
<span className="gc-time-estimate__value">{minutes}</span>
<span className="gc-time-estimate__unit">min</span>
</div>
)
}
// Timer Hook
export const useTimer = (duration: number) => {
const [remaining, setRemaining] = useState(duration)
const [isRunning, setIsRunning] = useState(false)
useEffect(() => {
if (!isRunning || remaining <= 0) return
const interval = setInterval(() => {
setRemaining((r) => r - 1)
}, 1000)
return () => clearInterval(interval)
}, [isRunning, remaining])
return {
remaining,
isRunning,
progress: ((duration - remaining) / duration) * 100,
start: () => setIsRunning(true),
pause: () => setIsRunning(false),
reset: () => {
setRemaining(duration)
setIsRunning(false)
},
}
}
Accessibility Considerations¶
Screen Reader Announcements¶
- Announce time estimates when cards receive focus
- Provide periodic updates during active timers (every 30 seconds)
- Announce warnings at 1 minute and completion
Keyboard Controls¶
- Space/Enter to start/pause timer
- Escape to reset timer
- Tab navigation through controls
Visual Indicators¶
- Use color AND icons for time categories
- Provide text labels alongside visual indicators
- Ensure sufficient contrast for all states
Testing Checklist¶
- Time estimates display correctly for all roles
- Timer counts down accurately
- Progress bars update smoothly
- Warnings appear at correct thresholds
- Screen reader announcements work
- Keyboard controls function properly
- Historical accuracy tracking works
- Adaptive estimates adjust appropriately
- Batch calculations show savings
- Mobile display remains clear
Conclusion¶
The Time Estimate Display System ensures transparency and sets clear expectations for all users, particularly time-pressed board directors. By showing accurate, role-specific time commitments upfront, GetCimple enables better planning and higher completion rates.