Build SafeSignal, a Mobile SOS App

Build a mobile SOS web app with sharing, location, and resilient fallbacks.

Introduction

30 Second Summary

In a stressful moment, even a simple message can become hard to organise. A trusted person needs the essential details quickly.

In this project, you will build SafeSignal, a phone-ready page that turns a short form into a timestamped SOS alert with optional location. This learning prototype creates plain text for a trusted recipient without contacting emergency services or confirming delivery.

What You'll Build

You will open SafeSignal on a phone, create a readable SOS alert, add your location or a nearby landmark, then send the message through the device share interface or copy it.

By the end of this project, you'll have:

  • A mobile-friendly SOS builder that turns a name, situation, essential details, and local time into a readable alert.
  • A location-aware message that can show coordinates, a Google Maps link, or a manually entered landmark.
  • A resilient sharing flow that opens the device share interface when available while keeping Copy SOS ready as a fallback.
  • Secret Mission: Run a failure-mode drill that tests denied location, canceled sharing, and an alert with no location.

Are there any prerequisites?

You need basic knowledge of HTML and JavaScript.

You also need an email address for a free GitHub account.

The final test uses a phone, with Google Chrome setup included if needed.

Before We Start

A realistic SOS scenario keeps every design choice focused on someone who may be stressed or in a hurry. This is your moment to define the sender, situation, essential details, location information, and trusted recipient before the hands-on work begins.

Set Up a Local SafeSignal Page

Your SOS scenario now gives every later design choice a clear purpose. The next risk is a simple folder or filename mismatch on Windows.

A tiny starter page exposes that problem before the emergency flow grows.

Visual Studio Code keeps the three files together. Google Chrome proves the browser can read them.

In this step, get ready to:
  • Create the local safesignal-sos folder with three project files.
  • Connect the stylesheet and script to the starter page.
  • Open the page in Google Chrome to verify the local path.
Create the SafeSignal project files

A folder opened in Visual Studio Code becomes a workspace. The Explorer view keeps every SafeSignal file visible in one place.

  • Press the Windows key to open the search bar.
  • Type Visual Studio Code.
  • Press Enter to open Visual Studio Code.

You'll see Visual Studio Code open without a SafeSignal folder yet.

  • Select File from the top menu.
  • Select Open Folder....

You'll see the Windows folder picker.

  • Select Desktop in the folder picker.
  • Select New Folder.

You'll see a field for the new folder name.

  • Type safesignal-sos into the folder name field.
  • Press Enter to create the folder.

The new safesignal-sos folder should now be selected on your Desktop.

Visual Studio Code may show a Workspace Trust dialog after opening the folder. You created this empty folder yourself, so its contents are safe to trust.

  • Select Select Folder to open safesignal-sos.
  • Select Yes, I trust the authors if the Workspace Trust dialog appears.

The Explorer sidebar should now show safesignal-sos as the open folder.

The New File... button can be easy to miss. It sits beside the folder name at the top of the Explorer.

  • Select the Explorer view in the left Activity Bar.
  • Select New File... beside the safesignal-sos folder name.

You'll see a filename field inside the Explorer.

  • Type index.html into the filename field.
  • Press Enter to create the file.

You'll see index.html in the Explorer.

  • Select New File... again.
  • Type styles.css into the filename field.

The filename is ready to be confirmed.

  • Press Enter to create styles.css.
  • Select New File... once more.

You'll see another filename field in the Explorer.

  • Type app.js into the filename field.
  • Press Enter to create the file.

You should now see index.html, styles.css, plus app.js together in the Explorer.

Missing a File in the Explorer?

Check that the Explorer shows safesignal-sos at the top. Use New File... beside that folder to create any missing file.

Help me check my SafeSignal folder structure in Visual Studio Code.

Connect the page files

HTML gives the starter page its visible structure. CSS controls how that structure looks.

JavaScript adds behavior in later steps. The defer attribute waits until the page structure has been read before running app.js.

  • Select index.html in the Explorer.
  • Add the starter page by pasting the code below into index.html:
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>SafeSignal</title>
    <link rel="stylesheet" href="styles.css" />
    <script src="app.js" defer></script>
  </head>
  <body>
    <main>
      <h1>SafeSignal</h1>
      <p>Build a clear SOS alert.</p>
    </main>
  </body>
</html>

What Does This Code Do?

  • The document structure gives Google Chrome a complete page to read.
  • The stylesheet link loads styles.css from the same folder.
  • The script element loads app.js after the page structure is ready.
  • The main element contains the heading plus the starter message.
  • Press Ctrl+S to save index.html.
  • Confirm that the index.html tab no longer indicates unsaved changes.

Seeing HTML Syntax Markers?

Compare every opening tag with its closing tag. Check that the linked filenames match the files in the Explorer.

Help me find a syntax problem in my starter HTML.

  • Select styles.css in the Explorer.
  • Add the starter font rule by pasting the code below into styles.css:
body {
  font-family: system-ui, sans-serif;
}

What Does This Style Do?

The body rule applies the device's system font to the whole page. This keeps the starter text readable without downloading a font.

  • Press Ctrl+S to save styles.css.
  • Confirm that the styles.css tab no longer indicates unsaved changes.

Seeing a CSS Syntax Marker?

Check that the rule starts with body { and finishes with a closing brace. Keep the font property between those braces.

Help me fix the starter CSS rule.

  • Select app.js in the Explorer.

The editor should be completely blank. An empty app.js is the exact starter state for this step.

✔️ Awesome, I've got everything!

Your three files match the starter state. Make sure each file is saved before moving to the browser check.

ⓧ I'd like to double check the full code

Your complete index.html should match this code:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>SafeSignal</title>
    <link rel="stylesheet" href="styles.css" />
    <script src="app.js" defer></script>
  </head>
  <body>
    <main>
      <h1>SafeSignal</h1>
      <p>Build a clear SOS alert.</p>
    </main>
  </body>
</html>

This is the complete starter page. It includes the visible text plus both file connections.

Your complete styles.css should match this code:

body {
  font-family: system-ui, sans-serif;
}

This is the complete starter stylesheet. It contains one system-font rule.

The complete app.js file contains no text. A blank editor is the correct result.

Open the starter page in Google Chrome

Opening the HTML file directly tests the local browser path. A successful page proves that Google Chrome can read index.html from the safesignal-sos folder.

  • Right-click index.html in the Explorer.
  • Select Reveal in File Explorer.

You'll see the safesignal-sos folder in Windows File Explorer.

  • Press the Windows key to open the search bar.
  • Type Google Chrome.
  • Press Enter to open Google Chrome.

Before you open the file, which two lines do you expect Google Chrome to display?

  • Drag index.html from File Explorer into the Google Chrome window.

You should see the heading SafeSignal above the text Build a clear SOS alert..

That first local check is complete. Your three-file SafeSignal page now loads through the correct browser path.

Page Missing or Showing the Wrong Text?

  • Check that the file is named index.html with no extra file extension.
  • Confirm that all three files appear inside the same safesignal-sos folder.
  • Return to Visual Studio Code to compare index.html with the full-code tab.

Help me find why my local SafeSignal page does not display the expected text.

Your local foundation is working. Next, you'll turn this starter page into the mobile SOS form plus its alert preview.

Build the SOS Draft Flow

Your starter page proves that Visual Studio Code can connect the three project files into one page that runs in Google Chrome.

A useful SOS page needs to turn a few essential details into a readable alert. Testing its first Share SOS control will also reveal whether that alert can leave the page.

In this step, get ready to:
  • Build the mobile SOS form and alert preview.
  • Add responsive styling with large controls and clear focus states.
  • Generate a timestamped alert and test the temporary sharing controls.
Build the mobile SOS form

The form keeps the emergency flow focused on the details a trusted contact needs first. Its status regions provide visible feedback without taking the user to another page.

  • Switch back to the index.html tab from earlier.
  • Select all the existing code in index.html.
  • Replace it with the page shell below:
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta
      name="description"
      content="SafeSignal builds a clear SOS alert with optional location sharing."
    />
    <title>SafeSignal</title>
    <link rel="stylesheet" href="styles.css" />
    <script src="app.js" defer></script>
  </head>
  <body>
    <main class="app-shell">
      <header class="hero">
        <p class="eyebrow">Browser-only learning prototype</p>
        <h1>SafeSignal</h1>
        <p>Build a clear SOS alert.</p>
      </header>
    </main>
  </body>
</html>

What does this structure do?

  • The description metadata gives the page a concise summary for browsers and search tools.
  • The app-shell class provides a container for every part of the interface.
  • The hero introduces SafeSignal as a browser-only learning prototype.
  • Save index.html.
  • Refresh the SafeSignal page in Google Chrome.

You will see the SafeSignal heading with a small prototype label above it. The starter message remains underneath.

Still seeing the old starter page?

Confirm that you saved the index.html file inside safesignal-sos.

Make sure Google Chrome is displaying that same local file. Help me check why my edited index.html is not updating in Google Chrome.

  • Find the closing </header> line in index.html.
  • Paste the form section below that line:
      <section class="card" aria-labelledby="form-heading">
        <h2 id="form-heading">What should your trusted contact know?</h2>

        <form id="sos-form">
          <label for="sender">Your name</label>
          <input id="sender" name="sender" autocomplete="name" required />

          <label for="situation">Situation</label>
          <select id="situation" name="situation" required>
            <option value="">Choose one</option>
            <option value="I need urgent help">I need urgent help</option>
            <option value="Medical emergency">Medical emergency</option>
            <option value="I feel unsafe">I feel unsafe</option>
            <option value="Vehicle trouble">Vehicle trouble</option>
          </select>
        </form>
      </section>

Why start with these fields?

The sender field identifies who needs help. The situation menu gives the alert a direct summary that is quick to scan.

The required attributes let the browser stop an incomplete submission before JavaScript builds the alert.

  • Save index.html.
  • Refresh the page in Google Chrome.

You will see a question followed by name and situation controls.

Form missing from the page?

Check that the new section sits inside <main class="app-shell">.

Confirm that every opening form or section tag has its matching closing tag. Help me find an HTML nesting problem in my SafeSignal form.

  • Find the closing </select> line inside sos-form.
  • Paste the detail fields directly below that line:
          <label for="details">Essential details</label>
          <textarea
            id="details"
            name="details"
            rows="4"
            placeholder="What happened? What help do you need?"
            required
          ></textarea>

          <label for="landmark">Nearby landmark if location is unavailable</label>
          <input
            id="landmark"
            name="landmark"
            placeholder="Station entrance, shop name, road marker..."
          />

How do these fields help?

Essential details capture the immediate context in the sender's own words. The landmark gives the recipient a useful location clue when coordinates are unavailable.

  • Save index.html.
  • Refresh the page in Google Chrome.

You will see a larger details area followed by the optional landmark field.

Details field looks like a single line?

Confirm that the essential-details control uses matching <textarea> and </textarea> tags.

Check that rows="4" remains inside the opening tag. Help me repair the textarea in my SafeSignal form.

  • Find the landmark input that ends with the road-marker placeholder.
  • Paste the location panel and submit button below its closing /> line:
          <div class="location-panel">
            <button
              class="button button-secondary"
              id="location-button"
              type="button"
            >
              Use my location
            </button>
            <p id="location-status" class="status" role="status">
              No location added yet.
            </p>
          </div>

          <button class="button button-primary" type="submit">
            Build SOS alert
          </button>

Why separate the location button?

The location control uses type="button" so it does not submit the form. The nearby status region has one job: report what happened after a location request.

The final button submits the completed form and starts the alert-building flow.

  • Save index.html.
  • Refresh the page in Google Chrome.

You will see a location button with its current status. The Build SOS alert button appears below it.

Build button appears inside the location panel?

Check that </div> closes the location panel before the submit button begins.

Keep both buttons inside sos-form so the form remains one complete flow. Help me fix the button nesting in my SafeSignal form.

  • Find the closing </section> line directly below </form>.
  • Paste the hidden alert preview below that line:
      <section class="card" id="preview-section" aria-labelledby="preview-heading" hidden>
        <h2 id="preview-heading">Your SOS alert</h2>
        <pre id="alert-preview" tabindex="0"></pre>

        <div class="button-row">
          <button class="button button-danger" id="share-button" type="button">
            Share SOS
          </button>
          <button class="button button-secondary" id="copy-button" type="button">
            Copy SOS
          </button>
        </div>

        <p id="action-status" class="status" data-tone="neutral" role="status">
          Your alert is ready.
        </p>
      </section>

How does the preview work?

The preview begins with the hidden attribute so an empty alert never appears. JavaScript removes that state after the form creates a message.

The <pre> element preserves the alert's line breaks. The two buttons keep sharing and copying available as separate actions.

  • Save index.html.
  • Refresh the page in Google Chrome.

The form remains visible. The empty alert preview stays hidden until JavaScript builds a message.

Seeing an empty alert card already?

Confirm that the opening preview section includes the hidden attribute.

Check that id="preview-section" appears on the same opening section tag. Help me hide the empty SafeSignal preview correctly.

  • Find the closing tag for preview-section.
  • Paste the safety notice directly below it:
      <aside class="safety-note">
        <h2>Important</h2>
        <p>
          This prototype does not contact emergency services, guarantee delivery,
          or encrypt the alert. Share only with a trusted recipient.
        </p>
      </aside>

Why include the safety notice?

SafeSignal creates plain text for a trusted recipient. It does not contact emergency services or confirm that anyone received the message.

Keeping that boundary beside the form prevents the prototype from making a misleading safety promise.

  • Save index.html.
  • Refresh the page in Google Chrome.

You will see the complete form followed by an Important notice. The alert preview remains hidden.

Safety notice outside the page shell?

Confirm that the entire safety-note aside appears before the closing </main> tag.

Keep the aside outside both card sections. Help me place the SafeSignal safety note inside app-shell.

✔️ Awesome, I've got everything!

Your complete SafeSignal structure is in place. Make sure index.html is saved before continuing.

ⓧ I'd like to double check the full code

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta
      name="description"
      content="SafeSignal builds a clear SOS alert with optional location sharing."
    />
    <title>SafeSignal</title>
    <link rel="stylesheet" href="styles.css" />
    <script src="app.js" defer></script>
  </head>
  <body>
    <main class="app-shell">
      <header class="hero">
        <p class="eyebrow">Browser-only learning prototype</p>
        <h1>SafeSignal</h1>
        <p>Build a clear SOS alert.</p>
      </header>

      <section class="card" aria-labelledby="form-heading">
        <h2 id="form-heading">What should your trusted contact know?</h2>

        <form id="sos-form">
          <label for="sender">Your name</label>
          <input id="sender" name="sender" autocomplete="name" required />

          <label for="situation">Situation</label>
          <select id="situation" name="situation" required>
            <option value="">Choose one</option>
            <option value="I need urgent help">I need urgent help</option>
            <option value="Medical emergency">Medical emergency</option>
            <option value="I feel unsafe">I feel unsafe</option>
            <option value="Vehicle trouble">Vehicle trouble</option>
          </select>

          <label for="details">Essential details</label>
          <textarea
            id="details"
            name="details"
            rows="4"
            placeholder="What happened? What help do you need?"
            required
          ></textarea>

          <label for="landmark">Nearby landmark if location is unavailable</label>
          <input
            id="landmark"
            name="landmark"
            placeholder="Station entrance, shop name, road marker..."
          />

          <div class="location-panel">
            <button
              class="button button-secondary"
              id="location-button"
              type="button"
            >
              Use my location
            </button>
            <p id="location-status" class="status" role="status">
              No location added yet.
            </p>
          </div>

          <button class="button button-primary" type="submit">
            Build SOS alert
          </button>
        </form>
      </section>

      <section class="card" id="preview-section" aria-labelledby="preview-heading" hidden>
        <h2 id="preview-heading">Your SOS alert</h2>
        <pre id="alert-preview" tabindex="0"></pre>

        <div class="button-row">
          <button class="button button-danger" id="share-button" type="button">
            Share SOS
          </button>
          <button class="button button-secondary" id="copy-button" type="button">
            Copy SOS
          </button>
        </div>

        <p id="action-status" class="status" data-tone="neutral" role="status">
          Your alert is ready.
        </p>
      </section>

      <aside class="safety-note">
        <h2>Important</h2>
        <p>
          This prototype does not contact emergency services, guarantee delivery,
          or encrypt the alert. Share only with a trusted recipient.
        </p>
      </aside>
    </main>
  </body>
</html>

What should you compare?

Check the order of the hero, form, preview, and safety note. Confirm that every ID matches this reference exactly.

Style the phone-first experience

Mobile-first responsive design starts with a layout that works on a narrow screen. A wider-screen rule can then enhance the controls without weakening the phone experience.

  • Switch back to the styles.css tab from earlier.
  • Select all the existing CSS.
  • Replace it with the visual foundation below:
:root {
  color-scheme: light;
  font-family:
    Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
    sans-serif;
  background: #f4f7fb;
  color: #172033;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  min-height: 100vh;
  background: linear-gradient(180deg, #e8eef8 0%, #f8fafc 40%);
}

button,
input,
select,
textarea {
  font: inherit;
}

What does this foundation control?

  • The root styles establish the page colors and shared font stack.
  • Universal border sizing keeps padding inside each element's declared width.
  • The form controls inherit the same readable font as the rest of the page.
  • Save styles.css.
  • Refresh the page in Google Chrome.

You will see a pale blue background with dark text and consistent typography.

Page still has a plain white background?

Confirm that index.html still links to styles.css.

Check that the CSS begins with :root and contains matching braces. Help me find why the first SafeSignal CSS styles are not loading.

  • Append the page-shell and hero styles to styles.css by adding the code below:
.app-shell {
  width: min(100% - 2rem, 44rem);
  margin: 0 auto;
  padding: 2rem 0 3rem;
}

.hero {
  margin-bottom: 1rem;
}

.hero h1 {
  margin: 0;
  font-size: clamp(2.25rem, 8vw, 4rem);
  line-height: 1;
}

.hero p {
  margin: 0.6rem 0 0;
}

.eyebrow {
  color: #43516c;
  font-size: 0.8rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

How does the shell stay readable?

The app shell leaves space around the page on narrow screens. Its maximum width prevents form lines from becoming difficult to scan on a desktop.

The heading uses clamp() to grow with the screen while staying inside a controlled size range.

  • Save styles.css.
  • Refresh the page in Google Chrome.

You will see a centered page with side spacing and a larger SafeSignal heading.

Content touching the screen edge?

Check that the shell width contains 100% - 2rem.

Confirm that the HTML main element uses class="app-shell" exactly. Help me restore the spacing around my SafeSignal page.

  • Append the card and form-layout styles to styles.css by adding the code below:
.card,
.safety-note {
  margin-top: 1rem;
  padding: 1.25rem;
  border: 1px solid #d9e0eb;
  border-radius: 1rem;
  background: #ffffff;
  box-shadow: 0 0.75rem 2rem rgba(30, 48, 80, 0.08);
}

.card h2,
.safety-note h2 {
  margin-top: 0;
}

form {
  display: grid;
  gap: 0.75rem;
}

label {
  margin-top: 0.35rem;
  font-weight: 750;
}

Why use cards and a grid?

The white cards separate the form from the safety notice. Rounded borders and a soft shadow keep that separation visible without adding clutter.

The form grid creates a steady vertical rhythm between labels and controls.

  • Save styles.css.
  • Refresh the page in Google Chrome.

You will see the form and safety notice inside separate white cards. The form labels now follow consistent spacing.

Cards still look like plain text?

Confirm that the form section uses the card class. Check that the safety aside uses safety-note.

Look for a missing comma between the two selectors. Help me fix the card styling for the SafeSignal form and safety note.

  • Append the control and focus styles to styles.css by adding the code below:
input,
select,
textarea {
  width: 100%;
  min-height: 3rem;
  padding: 0.75rem;
  border: 2px solid #bdc7d8;
  border-radius: 0.75rem;
  background: #ffffff;
  color: #172033;
}

textarea {
  resize: vertical;
}

input:focus,
select:focus,
textarea:focus,
button:focus-visible {
  outline: 3px solid #93c5fd;
  outline-offset: 2px;
  border-color: #1d4ed8;
}

Why make focus visible?

Large fields create easier touch targets on a phone. A strong blue outline shows which control currently has keyboard focus.

The resizable details area lets the sender reveal more of a longer message while reviewing it.

  • Save styles.css.
  • Refresh the page in Google Chrome.
  • Select the name field with the keyboard or pointer.

You will see wide controls with rounded borders. The selected field gains a clear blue outline.

Focus outline missing?

Check that each focus selector includes a colon before focus.

Confirm that commas separate the input, select, textarea, and button selectors. Help me fix the focus styles in SafeSignal.

  • Append the location-panel and base button styles to styles.css by adding the code below:
.location-panel {
  padding: 1rem;
  border-radius: 0.75rem;
  background: #eff6ff;
}

.button-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.75rem;
}

.button {
  min-height: 3.25rem;
  padding: 0.8rem 1rem;
  border: 0;
  border-radius: 0.75rem;
  font-weight: 800;
  cursor: pointer;
}

How does this support phones?

The location panel groups the permission action with its status. The base button style creates large targets that are easier to tap under pressure.

The button row starts as a single column so Share SOS and Copy SOS can use the full screen width.

  • Save styles.css.
  • Refresh the page in Google Chrome.

You will see the location control inside a pale blue panel. Both visible buttons are taller and easier to select.

Buttons still look small?

Confirm that both visible buttons include the button class in index.html.

Check that min-height: 3.25rem remains inside the button rule. Help me fix the SafeSignal button sizing.

  • Append the button-color and status styles to styles.css by adding the code below:
.button:disabled {
  cursor: wait;
  opacity: 0.65;
}

.button-primary {
  margin-top: 0.5rem;
  background: #173b75;
  color: #ffffff;
}

.button-secondary {
  background: #dbeafe;
  color: #173b75;
}

.button-danger {
  background: #b42318;
  color: #ffffff;
}

.status {
  margin: 0.75rem 0 0;
  color: #43516c;
}

What do the button colors communicate?

The dark blue primary button marks the form's main action. The lighter secondary style gives location and copying less visual weight.

The red danger style reserves the strongest emphasis for Share SOS. Disabled controls become faded while an action is in progress.

  • Save styles.css.
  • Refresh the page in Google Chrome.

You will see a dark blue Build SOS alert button and a pale blue location button. The location status uses muted text.

Button colors not changing?

Confirm that the submit button includes button-primary and the location button includes button-secondary.

Check that each class begins with a dot in styles.css. Help me match the SafeSignal button classes to their CSS selectors.

  • Append the status-tone and alert styles to styles.css by adding the code below:
.status[data-tone="success"] {
  color: #137044;
}

.status[data-tone="warning"] {
  color: #9a3412;
}

#alert-preview {
  overflow-x: auto;
  padding: 1rem;
  border-radius: 0.75rem;
  background: #101827;
  color: #f8fafc;
  font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
  line-height: 1.6;
  white-space: pre-wrap;
  word-break: break-word;
}

.safety-note {
  border-left: 0.4rem solid #b42318;
  background: #fff7f6;
}

How will feedback stay readable?

Status tones distinguish successful actions from warnings through text color. The alert preview uses a dark surface and fixed-width text so each line remains easy to scan.

The safety note uses a red edge to keep the prototype boundary visible below the workflow.

  • Save styles.css.
  • Refresh the page in Google Chrome.

You will see a pale red safety note with a strong red edge. The alert preview remains hidden until a message exists.

Safety note missing its red edge?

Confirm that the aside still uses class="safety-note".

Check the border-left declaration for a missing colon or brace. Help me fix the SafeSignal safety-note styling.

  • Append the final spacing, hidden-state, and wider-screen rules to styles.css by adding the code below:
.safety-note p {
  margin-bottom: 0;
}

[hidden] {
  display: none !important;
}

@media (min-width: 36rem) {
  .card,
  .safety-note {
    padding: 1.5rem;
  }

  .button-row {
    grid-template-columns: 1fr 1fr;
  }
}

What changes on wider screens?

The hidden rule keeps the empty preview out of the layout. At widths of 36rem or more, cards gain extra padding and the preview buttons can sit in two columns.

The narrow-screen layout remains the default, which keeps the phone experience in control.

  • Save styles.css.
  • Refresh the page in Google Chrome.
  • Resize the browser window across the 36rem breakpoint.

You will see the cards gain more inner spacing on the wider view. The form remains a single readable column on both sides of the breakpoint.

Layout overflowing on a narrow screen?

Confirm that the media rule uses min-width: 36rem and has its final closing brace.

Check that box-sizing: border-box remains near the top of the file. Help me diagnose horizontal overflow in the SafeSignal layout.

✔️ Awesome, I've got everything!

Your form now has a phone-first layout with large controls and visible focus states. Make sure styles.css is saved.

ⓧ I'd like to double check the full code

:root {
  color-scheme: light;
  font-family:
    Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
    sans-serif;
  background: #f4f7fb;
  color: #172033;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  min-height: 100vh;
  background: linear-gradient(180deg, #e8eef8 0%, #f8fafc 40%);
}

button,
input,
select,
textarea {
  font: inherit;
}

.app-shell {
  width: min(100% - 2rem, 44rem);
  margin: 0 auto;
  padding: 2rem 0 3rem;
}

.hero {
  margin-bottom: 1rem;
}

.hero h1 {
  margin: 0;
  font-size: clamp(2.25rem, 8vw, 4rem);
  line-height: 1;
}

.hero p {
  margin: 0.6rem 0 0;
}

.eyebrow {
  color: #43516c;
  font-size: 0.8rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.card,
.safety-note {
  margin-top: 1rem;
  padding: 1.25rem;
  border: 1px solid #d9e0eb;
  border-radius: 1rem;
  background: #ffffff;
  box-shadow: 0 0.75rem 2rem rgba(30, 48, 80, 0.08);
}

.card h2,
.safety-note h2 {
  margin-top: 0;
}

form {
  display: grid;
  gap: 0.75rem;
}

label {
  margin-top: 0.35rem;
  font-weight: 750;
}

input,
select,
textarea {
  width: 100%;
  min-height: 3rem;
  padding: 0.75rem;
  border: 2px solid #bdc7d8;
  border-radius: 0.75rem;
  background: #ffffff;
  color: #172033;
}

textarea {
  resize: vertical;
}

input:focus,
select:focus,
textarea:focus,
button:focus-visible {
  outline: 3px solid #93c5fd;
  outline-offset: 2px;
  border-color: #1d4ed8;
}

.location-panel {
  padding: 1rem;
  border-radius: 0.75rem;
  background: #eff6ff;
}

.button-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.75rem;
}

.button {
  min-height: 3.25rem;
  padding: 0.8rem 1rem;
  border: 0;
  border-radius: 0.75rem;
  font-weight: 800;
  cursor: pointer;
}

.button:disabled {
  cursor: wait;
  opacity: 0.65;
}

.button-primary {
  margin-top: 0.5rem;
  background: #173b75;
  color: #ffffff;
}

.button-secondary {
  background: #dbeafe;
  color: #173b75;
}

.button-danger {
  background: #b42318;
  color: #ffffff;
}

.status {
  margin: 0.75rem 0 0;
  color: #43516c;
}

.status[data-tone="success"] {
  color: #137044;
}

.status[data-tone="warning"] {
  color: #9a3412;
}

#alert-preview {
  overflow-x: auto;
  padding: 1rem;
  border-radius: 0.75rem;
  background: #101827;
  color: #f8fafc;
  font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
  line-height: 1.6;
  white-space: pre-wrap;
  word-break: break-word;
}

.safety-note {
  border-left: 0.4rem solid #b42318;
  background: #fff7f6;
}

.safety-note p {
  margin-bottom: 0;
}

[hidden] {
  display: none !important;
}

@media (min-width: 36rem) {
  .card,
  .safety-note {
    padding: 1.5rem;
  }

  .button-row {
    grid-template-columns: 1fr 1fr;
  }
}

What should you compare?

Check that every selector appears in this order. Pay close attention to closing braces around the media rule.

Turn the form into an alert

The DOM references connect JavaScript to the form, preview, buttons, and status regions. A small state value then keeps the latest generated alert available for later actions.

  • Switch back to the empty app.js tab from earlier.
  • Add the element references, state, status helper, and temporary button handlers by pasting:
const elements = {
  sosForm: document.getElementById("sos-form"),
  previewSection: document.getElementById("preview-section"),
  alertPreview: document.getElementById("alert-preview"),
  shareButton: document.getElementById("share-button"),
  copyButton: document.getElementById("copy-button"),
  actionStatus: document.getElementById("action-status"),
  locationButton: document.getElementById("location-button"),
  locationStatus: document.getElementById("location-status"),
};

let locationData = null;
let generatedAlert = "";

function setActionStatus(message, tone = "neutral") {
  elements.actionStatus.textContent = message;
  elements.actionStatus.dataset.tone = tone;
}

elements.shareButton.addEventListener("click", () => {
  setActionStatus("Sharing is not connected yet.", "warning");
});

elements.copyButton.addEventListener("click", () => {
  setActionStatus("Copying is not connected yet.", "warning");
});

What does this JavaScript prepare?

  • The elements object gathers every page element the interaction will update.
  • The generatedAlert value will hold the latest plain-text message.
  • The status helper updates both the visible message and its color tone.
  • The temporary button handlers make the unfinished sharing and copying controls report their current state.
  • Save app.js.
  • Refresh the page in Google Chrome.
  • Press Control+Shift+J to open the Console panel.
  • Check the form reference by running:
elements.sosForm

What does this check prove?

The console displays the form element when the ID lookup succeeds. This proves that app.js can reach sos-form in the page.

You should see the <form id="sos-form"> element in the console output.

Console shows null?

Confirm that the form ID is exactly sos-form in index.html.

Check that the script still uses defer so the HTML exists before the lookups run. Help me fix a null sosForm reference in SafeSignal.

  • Add the alert builder below setActionStatus() and above the temporary button handlers by pasting:
function buildAlert() {
  const formData = new FormData(elements.sosForm);
  const sender = formData.get("sender").trim();
  const situation = formData.get("situation");
  const details = formData.get("details").trim();
  const landmark = formData.get("landmark").trim();
  const time = new Date().toLocaleString();

  let locationLine = "Location: Not provided";

  if (locationData) {
    locationLine = [
      `Location: ${locationData.latitude}, ${locationData.longitude}`,
      `Map: ${locationData.mapUrl}`,
    ].join("\n");
  } else if (landmark) {
    locationLine = `Nearby landmark: ${landmark}`;
  }

  return [
    "SAFE SIGNAL SOS",
    `From: ${sender}`,
    `Situation: ${situation}`,
    `Details: ${details}`,
    `Time: ${time}`,
    locationLine,
    "Please contact me and call local emergency services if needed.",
  ].join("\n");
}

How is the message assembled?

  • FormData reads the current value from each named form control.
  • The local date and time give the recipient context for when the alert was created.
  • A typed landmark replaces the default location line when one is available.
  • The final array joins each message part with a new line for quick scanning.
  • Save app.js.
  • Refresh the page in Google Chrome.
  • Enter Alex in the name field.
  • Select I feel unsafe from the situation menu.
  • Enter I am waiting inside the station entrance. in the details field.
  • Leave the landmark field empty.
  • Return to the Console panel.
  • Preview the builder's return value by running:
buildAlert()

What should the function return?

The console displays a multi-line SOS message containing Alex, the selected situation, the details, and the local time. Its location line says Location: Not provided because the landmark field is empty.

This check proves the message logic works before the form submits it into the page.

Seeing an error from buildAlert()?

Confirm that every form control keeps the names sender, situation, details, and landmark.

Check the template strings for matching backticks. Help me debug the SafeSignal buildAlert function.

  • Add the preview updater and submit listener below buildAlert() and above the temporary button handlers by pasting:
function refreshAlert() {
  generatedAlert = buildAlert();
  elements.alertPreview.textContent = generatedAlert;
  elements.previewSection.hidden = false;
}

elements.sosForm.addEventListener("submit", (event) => {
  event.preventDefault();
  refreshAlert();
  setActionStatus("Your alert is ready to share or copy.", "success");
});

How does submission update the page?

The submit listener prevents the browser from navigating away. It then builds the alert and places that plain text inside the preview.

Removing the preview's hidden state makes the new message visible. The green status confirms that the draft is ready for its next action.

  • Save app.js.
  • Refresh the page in Google Chrome.

Before you test the finished draft, what information do you expect the preview to contain when the landmark is empty?

  • Enter Alex in the name field.
  • Select I feel unsafe from the situation menu.
  • Enter I am waiting inside the station entrance. in the details field.
  • Leave the landmark field empty.
  • Select Build SOS alert.

You will see a dark alert preview with the sender, situation, details, local time, and Location: Not provided. A green status reports that the alert is ready to share or copy.

That is the first complete SafeSignal draft working from end to end. The form now turns a few practice details into a message a trusted contact could scan quickly.

Before you select Share SOS, do you think the alert can reach another app yet?

  • Select Share SOS.

You will see Sharing is not connected yet. below the buttons. This shortfall is intentional because the draft currently exists only inside the page.

  • Select Copy SOS.

You will see Copying is not connected yet. while the alert remains visible. The interface reports its limits without claiming that the message was sent.

Preview does not appear?

Confirm that the submit listener sits outside every function. Check that refreshAlert() closes before the listener begins.

Make sure the preview ID is exactly preview-section in both files. Help me debug a SafeSignal form that does not reveal the alert preview.

✔️ Awesome, I've got everything!

Your JavaScript builds a timestamped alert and exposes the temporary sharing limitation. Make sure app.js is saved.

ⓧ I'd like to double check the full code

const elements = {
  sosForm: document.getElementById("sos-form"),
  previewSection: document.getElementById("preview-section"),
  alertPreview: document.getElementById("alert-preview"),
  shareButton: document.getElementById("share-button"),
  copyButton: document.getElementById("copy-button"),
  actionStatus: document.getElementById("action-status"),
  locationButton: document.getElementById("location-button"),
  locationStatus: document.getElementById("location-status"),
};

let locationData = null;
let generatedAlert = "";

function setActionStatus(message, tone = "neutral") {
  elements.actionStatus.textContent = message;
  elements.actionStatus.dataset.tone = tone;
}

function buildAlert() {
  const formData = new FormData(elements.sosForm);
  const sender = formData.get("sender").trim();
  const situation = formData.get("situation");
  const details = formData.get("details").trim();
  const landmark = formData.get("landmark").trim();
  const time = new Date().toLocaleString();

  let locationLine = "Location: Not provided";

  if (locationData) {
    locationLine = [
      `Location: ${locationData.latitude}, ${locationData.longitude}`,
      `Map: ${locationData.mapUrl}`,
    ].join("\n");
  } else if (landmark) {
    locationLine = `Nearby landmark: ${landmark}`;
  }

  return [
    "SAFE SIGNAL SOS",
    `From: ${sender}`,
    `Situation: ${situation}`,
    `Details: ${details}`,
    `Time: ${time}`,
    locationLine,
    "Please contact me and call local emergency services if needed.",
  ].join("\n");
}

function refreshAlert() {
  generatedAlert = buildAlert();
  elements.alertPreview.textContent = generatedAlert;
  elements.previewSection.hidden = false;
}

elements.sosForm.addEventListener("submit", (event) => {
  event.preventDefault();
  refreshAlert();
  setActionStatus("Your alert is ready to share or copy.", "success");
});

elements.shareButton.addEventListener("click", () => {
  setActionStatus("Sharing is not connected yet.", "warning");
});

elements.copyButton.addEventListener("click", () => {
  setActionStatus("Copying is not connected yet.", "warning");
});

What should you compare?

Check the function order and closing braces. Confirm that the three listeners appear outside the functions at the bottom of the file.

Your mobile form now produces a readable SOS draft and reports its current sharing limit honestly. Next up, you will connect the alert to the device share interface and add a dependable copy fallback.

Make the Alert Shareable

Your SafeSignal form now turns a few details into a clear SOS alert. However, that alert remains trapped inside the page.

This step uses progressive enhancement to add capabilities when the browser supports them. The Web Share API opens the device share interface after a deliberate tap.

The Clipboard API provides a copy route. Manual selection protects the flow when clipboard access fails.

In this step, get ready to:
  • Connect Share SOS to the device share interface.
  • Connect Copy SOS to the system clipboard.
  • Verify every sharing outcome without leaving the page.
Connect the device share interface

The browser allows navigator.share() to run after a user activates a control such as Share SOS. A resolved request confirms that the browser handed the alert to the selected target.

  • In app.js from earlier, find refreshAlert().
  • Add the following function below refreshAlert():
async function shareAlert() {
  if (!generatedAlert) {
    setActionStatus("Build the SOS alert before sharing it.", "warning");
    return;
  }

  try {
    await navigator.share({
      title: "SafeSignal SOS",
      text: generatedAlert,
    });
    setActionStatus("The alert was handed to your chosen share target.", "success");
  } catch {
    setActionStatus(
      "Sharing did not finish. Your alert is still ready for Copy SOS.",
      "warning",
    );
  }
}

What does this code do?

  • The first check stops sharing when generatedAlert is empty.
  • The share request passes the title SafeSignal SOS to the selected target.
  • The same request passes generatedAlert as the message body.
  • The catch block keeps the alert ready when sharing is canceled or fails.
  • Scroll to the temporary Share SOS listener at the bottom of app.js.
  • Delete the temporary Share SOS listener.
  • Add this replacement in the same location:
elements.shareButton.addEventListener("click", shareAlert);

if (!navigator.share) {
  elements.shareButton.hidden = true;
}

How does the share control adapt?

  • The click listener runs shareAlert only after the learner selects Share SOS.
  • The feature check hides Share SOS when navigator.share is unavailable.
  • Copy SOS remains visible as the fallback.
  • Save app.js.
  • Refresh the SafeSignal page in Google Chrome.
  • Select Share SOS before building an alert if the button is visible.

You will see Build the SOS alert before sharing it. in the status region. An unavailable share feature hides Share SOS while leaving Copy SOS visible.

Share SOS still uses the temporary message?

Check the bottom of app.js for the old temporary Share SOS listener. Remove it so the button has only the listener that calls shareAlert.

Help me check the Share SOS listener in app.js.

Add a dependable copy fallback

Automatic clipboard access can be unavailable or blocked. The fallback focuses the alert preview so the complete message is ready for manual selection.

  • In app.js, find shareAlert().
  • Add the following function directly above shareAlert():
async function copyAlert() {
  if (!generatedAlert) {
    setActionStatus("Build the SOS alert before copying it.", "warning");
    return;
  }

  if (!navigator.clipboard?.writeText) {
    setActionStatus(
      "Clipboard access is unavailable. Select the alert text and copy it manually.",
      "warning",
    );
    elements.alertPreview.focus();
    return;
  }

  try {
    await navigator.clipboard.writeText(generatedAlert);
    setActionStatus("SOS alert copied.", "success");
  } catch {
    setActionStatus(
      "Copying did not finish. Select the alert text and copy it manually.",
      "warning",
    );
    elements.alertPreview.focus();
  }
}

What does this code do?

  • The first check stops copying when no alert has been generated.
  • The feature check detects whether clipboard writing is available.
  • The clipboard request writes the full generatedAlert string.
  • Both fallback paths focus alertPreview for manual copying.
  • Scroll to the temporary Copy SOS listener at the bottom of app.js.
  • Delete the temporary Copy SOS listener.
  • Add this replacement above the Share SOS listener:
elements.copyButton.addEventListener("click", copyAlert);

How is copying activated?

This listener connects the Copy SOS button to copyAlert(). The clipboard request begins only after the button click.

  • Save app.js.
  • Refresh the SafeSignal page.
  • Select Copy SOS before building an alert.

You will see Build the SOS alert before copying it. in the status region. The browser does not attempt clipboard access.

Copy SOS still uses the temporary message?

Remove the old Copy SOS listener from the bottom of app.js. Keep only the listener that passes copyAlert to addEventListener().

Help me check the Copy SOS listener in app.js.

Your sharing code is complete. Use these tabs to confirm that your app.js matches the finished version for this step.

✔️ Awesome, I've got everything!

Great. Double-check that you saved app.js.

ⓧ I'd like to double check the full code

const elements = {
  sosForm: document.getElementById("sos-form"),
  previewSection: document.getElementById("preview-section"),
  alertPreview: document.getElementById("alert-preview"),
  shareButton: document.getElementById("share-button"),
  copyButton: document.getElementById("copy-button"),
  actionStatus: document.getElementById("action-status"),
  locationButton: document.getElementById("location-button"),
  locationStatus: document.getElementById("location-status"),
};

let locationData = null;
let generatedAlert = "";

function setActionStatus(message, tone = "neutral") {
  elements.actionStatus.textContent = message;
  elements.actionStatus.dataset.tone = tone;
}

function buildAlert() {
  const formData = new FormData(elements.sosForm);
  const sender = formData.get("sender").trim();
  const situation = formData.get("situation");
  const details = formData.get("details").trim();
  const landmark = formData.get("landmark").trim();
  const time = new Date().toLocaleString();

  let locationLine = "Location: Not provided";

  if (locationData) {
    locationLine = [
      `Location: ${locationData.latitude}, ${locationData.longitude}`,
      `Map: ${locationData.mapUrl}`,
    ].join("\n");
  } else if (landmark) {
    locationLine = `Nearby landmark: ${landmark}`;
  }

  return [
    "SAFE SIGNAL SOS",
    `From: ${sender}`,
    `Situation: ${situation}`,
    `Details: ${details}`,
    `Time: ${time}`,
    locationLine,
    "Please contact me and call local emergency services if needed.",
  ].join("\n");
}

function refreshAlert() {
  generatedAlert = buildAlert();
  elements.alertPreview.textContent = generatedAlert;
  elements.previewSection.hidden = false;
}

async function copyAlert() {
  if (!generatedAlert) {
    setActionStatus("Build the SOS alert before copying it.", "warning");
    return;
  }

  if (!navigator.clipboard?.writeText) {
    setActionStatus(
      "Clipboard access is unavailable. Select the alert text and copy it manually.",
      "warning",
    );
    elements.alertPreview.focus();
    return;
  }

  try {
    await navigator.clipboard.writeText(generatedAlert);
    setActionStatus("SOS alert copied.", "success");
  } catch {
    setActionStatus(
      "Copying did not finish. Select the alert text and copy it manually.",
      "warning",
    );
    elements.alertPreview.focus();
  }
}

async function shareAlert() {
  if (!generatedAlert) {
    setActionStatus("Build the SOS alert before sharing it.", "warning");
    return;
  }

  try {
    await navigator.share({
      title: "SafeSignal SOS",
      text: generatedAlert,
    });
    setActionStatus("The alert was handed to your chosen share target.", "success");
  } catch {
    setActionStatus(
      "Sharing did not finish. Your alert is still ready for Copy SOS.",
      "warning",
    );
  }
}

elements.sosForm.addEventListener("submit", (event) => {
  event.preventDefault();
  refreshAlert();
  setActionStatus("Your alert is ready to share or copy.", "success");
});

elements.copyButton.addEventListener("click", copyAlert);
elements.shareButton.addEventListener("click", shareAlert);

if (!navigator.share) {
  elements.shareButton.hidden = true;
}
Test every sharing outcome

The final check uses made-up details so no real emergency information enters another app or the clipboard. Each result stays visible in SafeSignal's status region.

  • Refresh the SafeSignal page to clear the generated alert.
  • Select Copy SOS.

You will see Build the SOS alert before copying it..

  • Select Share SOS if the button is visible.

You will see Build the SOS alert before sharing it..

  • Enter Alex Morgan in Your name.
  • Select Vehicle trouble from Situation.
  • Enter Flat tyre near the station entrance. in Essential details.
  • Select Build SOS alert.

The alert preview will show the sender. It will also show the situation, details, local time, and Location: Not provided.

Before you test the fallback, do you expect the alert to remain on the page after Copy SOS runs?

  • Select Copy SOS.

When clipboard access succeeds, you will see SOS alert copied. The complete alert remains visible.

If clipboard access is blocked, you will see manual-copy guidance. The alert preview receives focus so you can select its text.

Before you test device sharing, predict whether Share SOS will be visible in your browser.

  • Select Share SOS if the button is visible.
  • Cancel the device share interface.

You will see Sharing did not finish. Your alert is still ready for Copy SOS. The alert remains available on the page.

  • Select Share SOS again.
  • Complete the handoff to a trusted test target.

You will see The alert was handed to your chosen share target. This status confirms the browser handoff without claiming that the message was delivered.

A hidden Share SOS button confirms that the feature is unavailable in this browser. Copy SOS remains visible as the supported route.

Copying did not finish?

Keep the alert preview visible. Use the manual-copy guidance when the browser blocks clipboard access on the local page.

Help me diagnose the SafeSignal clipboard fallback.

That is the sharing layer working: SafeSignal can hand off an alert or keep it ready for copying. Next, you will attach live location while preserving the manual landmark fallback.

Attach Live Location Safely

Your SOS alert can now leave the page through sharing or copying. A text-only alert still leaves a trusted contact guessing where to look.

The Geolocation API can add coordinates after a deliberate tap. SafeSignal keeps a typed landmark available whenever the location request does not succeed.

In this step, get ready to:
  • Add coordinate and landmark fallbacks to each alert.
  • Request location through the browser permission flow.
  • Refresh an existing alert after location succeeds.
Add location fallbacks to the alert

The alert needs one clear location line in every outcome. Coordinates take priority when available. A typed landmark comes next. The final fallback states that no location was provided.

  • In app.js, find the location fallback inside buildAlert().
  • Replace that location section with this code:
  let locationLine = "Location: Not provided";

  if (locationData) {
    locationLine = [
      `Location: ${locationData.latitude}, ${locationData.longitude}`,
      `Map: ${locationData.mapUrl}`,
    ].join("\n");
  } else if (landmark) {
    locationLine = `Nearby landmark: ${landmark}`;
  }

How does the fallback order work?

  • The initial locationLine makes the missing-location state explicit.
  • The first branch adds coordinates plus a map URL when locationData exists.
  • The second branch uses the form's landmark value when coordinates are unavailable.
  • Save app.js.
  • Refresh Google Chrome.
  • Enter Alex in the Your name field.
  • Select Vehicle trouble in the Situation field.
  • Enter My car has stopped and I need help. in the Essential details field.
  • Enter North station entrance in the landmark field.
  • Select Build SOS alert.

You should see Nearby landmark: North station entrance in the alert preview. This proves the manual fallback remains useful without browser coordinates.

Landmark missing from the alert?

Check that the landmark branch remains inside buildAlert(). Confirm that landmark matches the value read from the form.

Help me debug the landmark fallback in buildAlert().

Request location after a deliberate tap

Location access begins only after the user selects Use my location. The successful result becomes a rounded coordinate pair plus a Google Maps search URL.

The complete requestLocation() function is split across the next two code blocks for readability. Paste both parts together before saving the file.

  • In app.js, place your cursor after shareAlert().
  • Add the first part of requestLocation() with this code:
function requestLocation() {
  if (!navigator.geolocation) {
    elements.locationStatus.textContent =
      "Location is unavailable in this browser. Enter a nearby landmark instead.";
    return;
  }

  elements.locationButton.disabled = true;
  elements.locationStatus.textContent = "Requesting your current location...";

  navigator.geolocation.getCurrentPosition(
    (position) => {
      const latitude = position.coords.latitude.toFixed(5);
      const longitude = position.coords.longitude.toFixed(5);
      const query = encodeURIComponent(`${latitude},${longitude}`);

      locationData = {
        latitude,
        longitude,
        mapUrl: `https://www.google.com/maps/search/?api=1&query=${query}`,
      };

      elements.locationStatus.textContent =
        "Location added. Build the alert again to review it.";
      elements.locationButton.disabled = false;

What does the first part do?

  • The support check keeps the landmark route available when the browser has no geolocation feature.
  • Disabling locationButton prevents repeated requests while the browser is locating the user.
  • The success callback rounds each coordinate to five decimal places with toFixed(5).
  • The encoded query becomes the coordinate value in the map URL.
  • Continue directly beneath the first part with this code:
      if (generatedAlert) {
        refreshAlert();
        setActionStatus("The alert preview now includes your location.", "success");
      }
    },
    () => {
      locationData = null;
      elements.locationStatus.textContent =
        "Location was not added. Enter a nearby landmark instead.";
      elements.locationButton.disabled = false;
    },
    {
      enableHighAccuracy: true,
      timeout: 10000,
      maximumAge: 0,
    },
  );
}

What does the second part do?

  • An existing alert is rebuilt immediately after coordinates arrive.
  • A failed or denied request clears locationData while preserving the landmark route.
  • The options request a current high-accuracy position with a ten-second timeout.
  • Setting maximumAge to 0 prevents the browser from returning a cached position.
  • Find the event listeners near the bottom of app.js.
  • Add the location-button listener with this code:
elements.locationButton.addEventListener("click", requestLocation);

Why connect the button here?

This DOM event listener calls requestLocation() only after the location button is selected. That deliberate action keeps the permission request tied to a clear user choice.

The browser permission controls whether coordinates are returned. SafeSignal holds a successful result in memory for the current page instead of sending it to a backend.

Before you test the permission flow, predict how the location button and status text should behave while the browser waits for a result.

  • Save app.js.
  • Refresh Google Chrome.
  • Select Use my location.
  • Grant location access when the browser displays its permission prompt.

The button becomes unavailable while the request is running. After a successful result, you should see Location added. Build the alert again to review it.

Location was not added?

A denied request or a timed-out lookup leaves the button available for another attempt. Enter a nearby landmark to keep the alert useful.

Help me diagnose the SafeSignal location request.

Verify location refresh and fallbacks

A location result matters most when it reaches the alert itself. This final check starts with an existing no-location alert so you can watch the preview update after the request succeeds.

  • Refresh Google Chrome to reset the current in-memory alert.
  • Enter Alex in the Your name field.
  • Select Vehicle trouble in the Situation field.
  • Enter My car has stopped and I need help. in the Essential details field.
  • Leave the landmark field empty.
  • Select Build SOS alert.

Before you request location, predict which line in the current preview should change after coordinates arrive.

  • Select Use my location.
  • Grant location access if the browser asks again.

You should see the existing preview refresh with rounded latitude and longitude values. The next line should begin with Map: https://www.google.com/maps/search/?api=1&query=.

If the request is denied or unavailable, the status directs you to the landmark field. Rebuilding with a landmark shows that text in place of coordinates. Leaving both sources empty keeps Location: Not provided in the alert.

Preview did not refresh?

Confirm that an alert existed before you selected Use my location. Check that the success callback calls refreshAlert() inside the generatedAlert condition.

Help me fix the automatic location refresh.

✔️ Awesome, I've got everything!

Your location request, map URL, alert refresh, and manual fallback are now connected. Double-check that you saved app.js.

ⓧ I'd like to double check the full code

const elements = {
  sosForm: document.getElementById("sos-form"),
  previewSection: document.getElementById("preview-section"),
  alertPreview: document.getElementById("alert-preview"),
  shareButton: document.getElementById("share-button"),
  copyButton: document.getElementById("copy-button"),
  actionStatus: document.getElementById("action-status"),
  locationButton: document.getElementById("location-button"),
  locationStatus: document.getElementById("location-status"),
};

let locationData = null;
let generatedAlert = "";

function setActionStatus(message, tone = "neutral") {
  elements.actionStatus.textContent = message;
  elements.actionStatus.dataset.tone = tone;
}

function buildAlert() {
  const formData = new FormData(elements.sosForm);
  const sender = formData.get("sender").trim();
  const situation = formData.get("situation");
  const details = formData.get("details").trim();
  const landmark = formData.get("landmark").trim();
  const time = new Date().toLocaleString();

  let locationLine = "Location: Not provided";

  if (locationData) {
    locationLine = [
      `Location: ${locationData.latitude}, ${locationData.longitude}`,
      `Map: ${locationData.mapUrl}`,
    ].join("\n");
  } else if (landmark) {
    locationLine = `Nearby landmark: ${landmark}`;
  }

  return [
    "SAFE SIGNAL SOS",
    `From: ${sender}`,
    `Situation: ${situation}`,
    `Details: ${details}`,
    `Time: ${time}`,
    locationLine,
    "Please contact me and call local emergency services if needed.",
  ].join("\n");
}

function refreshAlert() {
  generatedAlert = buildAlert();
  elements.alertPreview.textContent = generatedAlert;
  elements.previewSection.hidden = false;
}

async function copyAlert() {
  if (!generatedAlert) {
    setActionStatus("Build the SOS alert before copying it.", "warning");
    return;
  }

  if (!navigator.clipboard?.writeText) {
    setActionStatus(
      "Clipboard access is unavailable. Select the alert text and copy it manually.",
      "warning",
    );
    elements.alertPreview.focus();
    return;
  }

  try {
    await navigator.clipboard.writeText(generatedAlert);
    setActionStatus("SOS alert copied.", "success");
  } catch {
    setActionStatus(
      "Copying did not finish. Select the alert text and copy it manually.",
      "warning",
    );
    elements.alertPreview.focus();
  }
}

async function shareAlert() {
  if (!generatedAlert) {
    setActionStatus("Build the SOS alert before sharing it.", "warning");
    return;
  }

  try {
    await navigator.share({
      title: "SafeSignal SOS",
      text: generatedAlert,
    });
    setActionStatus("The alert was handed to your chosen share target.", "success");
  } catch {
    setActionStatus(
      "Sharing did not finish. Your alert is still ready for Copy SOS.",
      "warning",
    );
  }
}

function requestLocation() {
  if (!navigator.geolocation) {
    elements.locationStatus.textContent =
      "Location is unavailable in this browser. Enter a nearby landmark instead.";
    return;
  }

  elements.locationButton.disabled = true;
  elements.locationStatus.textContent = "Requesting your current location...";

  navigator.geolocation.getCurrentPosition(
    (position) => {
      const latitude = position.coords.latitude.toFixed(5);
      const longitude = position.coords.longitude.toFixed(5);
      const query = encodeURIComponent(`${latitude},${longitude}`);

      locationData = {
        latitude,
        longitude,
        mapUrl: `https://www.google.com/maps/search/?api=1&query=${query}`,
      };

      elements.locationStatus.textContent =
        "Location added. Build the alert again to review it.";
      elements.locationButton.disabled = false;

      if (generatedAlert) {
        refreshAlert();
        setActionStatus("The alert preview now includes your location.", "success");
      }
    },
    () => {
      locationData = null;
      elements.locationStatus.textContent =
        "Location was not added. Enter a nearby landmark instead.";
      elements.locationButton.disabled = false;
    },
    {
      enableHighAccuracy: true,
      timeout: 10000,
      maximumAge: 0,
    },
  );
}

elements.sosForm.addEventListener("submit", (event) => {
  event.preventDefault();
  refreshAlert();
  setActionStatus("Your alert is ready to share or copy.", "success");
});

elements.locationButton.addEventListener("click", requestLocation);
elements.copyButton.addEventListener("click", copyAlert);
elements.shareButton.addEventListener("click", shareAlert);

if (!navigator.share) {
  elements.shareButton.hidden = true;
}

SafeSignal can now add useful location context without trapping the user in a permission-dependent flow. Next up, you will publish the app over HTTPS and test the complete experience on a phone.

Publish and Test on a Phone

SafeSignal already creates a readable alert with location fallbacks. The final milestone is to make that resilient flow available on your phone.

Location, clipboard, and sharing are designed around a secure browser context. GitHub Pages gives you a public HTTPS site that Google Chrome can open on your phone.

In this step, get ready to:
  • Create a free GitHub account with a public SafeSignal repository.
  • Upload your project files and publish them through GitHub Pages.
  • Run the complete SOS flow in Google Chrome on your phone.
Create your GitHub account and repository

A repository on GitHub stores the files that make up your site. GitHub Free gives you the public repository needed for this project at no cost.

  • Open the GitHub signup page.
  • Complete the signup form with an email address you can access.
  • Use the message from GitHub to verify your email address.

Your verified account can now create repositories. This verification also allows changes on your publishing branch to trigger a GitHub Pages build.

Keep the Public Project Fictional

Your repository and GitHub Pages site are public. Keep passwords, access tokens, real emergency details, and private personal data out of every file.

  • Return to GitHub with your verified account.
  • Open the creation menu in the upper-right corner.
  • Click New repository.

You will see the form that defines where SafeSignal lives and who can view it.

  • Select your personal account from the Owner menu.
  • Enter safesignal-sos in the Repository name field.

The repository name now matches your local safesignal-sos folder.

  • Select Public under repository visibility.
  • Toggle Add README to On.

The public setting makes the repository eligible for this GitHub Free publishing path. The README gives the new repository a starting file on its default branch.

  • Click Create repository.

Your public repository is ready. SafeSignal now has an online home for its final files.

Unable to Create the Repository?

Confirm that your email address is verified. Check that the repository name is exactly safesignal-sos.

If the form still blocks you, help me diagnose why I cannot create my SafeSignal repository.

Upload the files and publish the site

GitHub Pages publishes your HTML, CSS, and JavaScript directly from the repository. The top-level index.html file becomes the entry point for SafeSignal.

  • Return to the safesignal-sos repository from earlier.
  • Click Add file above the file list.

You will see the repository file menu with options for adding your finished project.

  • Click Upload files.
  • Click choose your files.

The Windows file picker lets you select the final files from your local project.

  • Select index.html, styles.css, and app.js from your local safesignal-sos folder.

You should see all three file names queued on the upload page.

  • Enter Upload SafeSignal files in the Commit message field.

The commit message now identifies the change that adds your finished app.

  • Choose to add the commit to the current main branch.
  • Click Propose changes.

You should see index.html, styles.css, and app.js in the repository on the main branch.

Missing a Project File?

Return to the repository file list to identify the missing file. Repeat the upload flow for that file from your local safesignal-sos folder.

If an uploaded file has the wrong name, help me compare my local SafeSignal files with my GitHub repository.

  • Click Settings under the repository name.
  • Select Pages in the sidebar.

The Build and deployment section controls which repository files become your public website.

  • Set Source to Deploy from a branch.
  • Set the publishing branch to main.

GitHub Pages now knows which branch contains your finished SafeSignal files.

  • Set the publishing folder to / (root).
  • Click Save.

GitHub Pages can take up to 10 minutes to publish the first version. A short wait here is normal while GitHub builds the HTTPS site.

  • Return to Pages after the deployment finishes.
  • Click Visit site under GitHub Pages.

You should see the SafeSignal form at an address beginning with https://.

  • Record the deployed address here: your SafeSignal HTTPS address.

Site Still Unavailable?

Confirm that the publishing branch is main. Confirm that the publishing folder is / (root).

Check that index.html appears at the top level of the repository. If the site still does not load after 10 minutes, help me troubleshoot my SafeSignal GitHub Pages deployment.

Test the complete flow on your phone

A phone test proves that your mobile layout works outside your computer. It also checks the real browser behavior of the Web Share API, Clipboard API, and Geolocation API.

Need Google Chrome?

  • Open Google Chrome on your phone.
  • Enter your SafeSignal HTTPS address in the address bar.

You should see the same SafeSignal form in a single-column phone layout with large controls.

  • Tap Use my location.
  • Choose whether to grant location access when Google Chrome prompts you.

If you grant access, you will see Location added. Build the alert again to review it. If you deny access, you will see Location was not added. Enter a nearby landmark instead.

  • Complete the required form fields with fictional test details.
  • Enter a fictional landmark if location was not added.

Before you build the alert, which location line do you expect to see based on the permission choice you made?

  • Tap Build SOS alert.

You should see a timestamped alert with your fictional sender, situation, and details. Its location line will contain coordinates, your landmark, or Location: Not provided.

✔️ Share SOS is visible

Before you test sharing, do you expect SafeSignal to claim that the alert was delivered?

  • Tap Share SOS.

You should see your phone's share interface with the SOS text ready. SafeSignal only hands the alert to that interface, so it never claims successful delivery.

Share Interface Did Not Open?

Confirm that you tapped Share SOS directly. The browser requires sharing to begin from a user action.

Use Copy SOS as the fallback. For more help, help me troubleshoot SafeSignal sharing on my phone.

ⓧ Share SOS is hidden

SafeSignal has detected that the browser does not offer its sharing feature. The copy route stays visible so the alert remains usable.

Before you test the fallback, what status should confirm that the alert reached your clipboard?

  • Tap Copy SOS.

You should see SOS alert copied. below the buttons.

Alert Did Not Copy?

Keep the site open over HTTPS. If clipboard access fails, SafeSignal directs you to select the alert text for manual copying.

For more help, help me troubleshoot the SafeSignal clipboard fallback.

You made it. SafeSignal now runs as a public HTTPS app with location, landmark, sharing, and copy fallbacks tested on a phone.

Secret mission

Run a Failure-Mode Drill

Test SafeSignal when location is denied, sharing is canceled, or no location is available. Record each visible result to prove the alert stays useful without claiming delivery.

Clean Up Your Resources

Clean Up Your Resources

Your local folder, public GitHub repository, and GitHub Pages site cost $0 on this project's path. Choose how much of SafeSignal you want to keep available.

Resources you used:

  • The local safesignal-sos folder containing index.html, styles.css, and app.js.
  • The public safesignal-sos repository on GitHub Free.
  • The public GitHub Pages deployment from the main branch and root / folder.

Keep Public Data Safe

The repository and website are publicly available on the internet. The source files should contain only fictional test details.

Passwords, access tokens, and private personal data have no place in index.html, styles.css, or app.js.

Keep everything running

No action needed. Choose this if you want SafeSignal available for future testing or continued development.

  • Leave the public safesignal-sos repository unchanged.
  • Leave the GitHub Pages deployment published.
  • Keep the local safesignal-sos folder on your Windows computer.
  • Use fictional information whenever you test the public site.

Pause - I'll come back to this later

There are no local servers or running processes to stop. Pausing means unpublishing the website while preserving both copies of your source files.

  • Switch back to the safesignal-sos repository on GitHub.
  • Select Settings from the repository navigation.
  • Select Pages from the sidebar.
  • Open the menu beside Your site is live at.
  • Select Unpublish site.
  • Confirm the GitHub Pages area no longer shows the site as live.

The public website is now offline. Your repository files remain on the main branch.

Your local safesignal-sos folder also remains ready for future changes.

Delete - I don't want to use this again

These deletion steps are permanent. Your GitHub account stays active while the SafeSignal repository, public site, and local folder are removed.

Remove the public site:

  • Switch back to the safesignal-sos repository on GitHub.
  • Select Settings from the repository navigation.
  • Select Pages from the sidebar.
  • Open the menu beside Your site is live at.
  • Select Unpublish site.
  • Confirm the GitHub Pages area no longer shows the site as live.

The public SafeSignal site is now offline. The repository and local folder still hold your code.

Delete the GitHub repository:

  • Select General from the repository settings sidebar.
  • Scroll to Danger Zone.
  • Select Delete this repository.
  • Complete the confirmation steps shown by GitHub.
  • Confirm the safesignal-sos repository no longer appears in your GitHub repositories.

The online SafeSignal project is now removed. Your local safesignal-sos folder is the final copy.

Delete the local folder:

  • Return to the Windows location where you created the safesignal-sos folder.
  • Select the safesignal-sos folder.
  • Use the Windows permanent-delete option to remove the selected folder.
  • Confirm the permanent deletion in the Windows prompt.
  • Confirm the safesignal-sos folder no longer appears in its previous location.

The SafeSignal files and public deployment are now removed. Your verified GitHub account remains active for other projects.

Nice Work!

Nice Work!

Fantastic work. SafeSignal is now a public phone-ready HTTPS app that turns a short form into a timestamped SOS alert.

You've learned how to:

  • Build a mobile-first SOS flow that turns form details into a readable alert. Publish it through GitHub Pages for testing on a phone.
  • Use the Web Share API to open the device share interface. Keep Copy SOS available through the Clipboard API as a fallback.
  • Add permission-based location through the Geolocation API with a Google Maps link. Preserve a typed landmark or Location: Not provided when coordinates are unavailable.
  • Secret Mission: Proved denied location can fall back to a landmark. Verified canceled sharing leaves Copy SOS available. Confirmed a missing location stays explicit without any false delivery claim.

Ready to quiz yourself?