Extract Opportunities into its own module; track Conference Scout as code
- opportunities/: opportunities.html (markup), opportunities.js (calendar, event list, scout list, drill-view logic), opportunities.css — loaded by index.html via fetch + dynamic <script>, replacing the inline copy that used to live directly in the monolith - Shared shell (index.html) no longer hardcodes Opportunities-specific drill-view config; the module registers itself into HERO_NAV_BY_TAB / DRILL_CONFIG / DRILL_DEFAULT_VIEW at load time, and setDrillView now tolerates scopes that haven't registered yet - agents/conference-scout/: the scheduled cloud routine's actual definition (schedule, model, tools, full prompt) as a tracked config.json, plus a README covering where it really runs (Anthropic's cloud, no server-side component here), how to keep it in sync, how to recreate it if deleted, and its current WebFetch/egress limitation Team/Directory/Marketing/Sales remain inline in index.html for now — a separate, larger pass given their size and interdependencies. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6afaefc251
commit
8d82a6d2ca
6 files changed
+559
-456
No files matched your search
@@ -0,0 +1,32 @@
|
||||
# Conference Scout
|
||||
|
||||
A scheduled cloud agent (Claude Code "routine") that searches the web every Monday morning for conferences, trade shows, seminars, and networking events relevant to TXproducts, and produces a report for manual review. It does not write to this repo, any calendar, or any file — a person reviews its report and decides what to add to the Opportunities tab's Events list themselves.
|
||||
|
||||
## Where it actually runs
|
||||
|
||||
This agent does **not** run on TXproducts infrastructure and has no server-side component anywhere. It runs entirely as a scheduled routine on Anthropic's cloud (via the Claude Code "routines" feature), triggered by cron and executed in an isolated sandbox with `WebSearch`/`WebFetch` access only. There is no code to deploy or host — the routine's entire behavior is defined by the prompt in [`config.json`](./config.json).
|
||||
|
||||
Live routine: https://claude.ai/code/routines/trig_01XRb81y9pAWDube5kjRUvGT
|
||||
|
||||
## What's in this folder
|
||||
|
||||
- **`config.json`** — the routine's definition: schedule, model, allowed tools, and the full prompt that defines its scope (what counts as "relevant," what's explicitly excluded, geographic coverage, output format). This is the actual source of truth for *what the agent does* — read the prompt to understand its behavior.
|
||||
|
||||
**This file is a manually-maintained mirror, not a live sync.** If someone edits the routine directly on claude.ai, this file goes stale until someone updates it here to match. There is currently no automation keeping the two in sync.
|
||||
|
||||
## How to update it
|
||||
|
||||
1. Change the routine itself first (via the Claude Code `RemoteTrigger` tool's `update` action, or the claude.ai UI), and confirm the change actually took by fetching it back (`RemoteTrigger` `get` action).
|
||||
2. Copy the confirmed live prompt/schedule/config into `config.json` here, update `_live_reference.last_synced_from_live`, and commit.
|
||||
|
||||
## How to recreate it if it's ever deleted
|
||||
|
||||
`config.json`'s top-level shape (`name`, `cron_expression`, `enabled`, `job_config`) matches the `RemoteTrigger` `create` action's request body directly — it can be passed to recreate the routine from scratch (you'll get a new `trigger_id`; update `_live_reference` afterward).
|
||||
|
||||
## Why results aren't fully automatic
|
||||
|
||||
Each run is a fresh, isolated cloud session with no memory of prior runs and no write access to this repo (by design — see the prompt's explicit "do NOT add anything to any file, calendar, or repository" instruction). Syncing a run's results into the Opportunities tab's Conference Scout view (`opportunities/opportunities.js`, `SCOUT_RESULTS`) is a manual step done by whoever reviews that week's report.
|
||||
|
||||
## Known limitation (as of 2026-08-17)
|
||||
|
||||
The routine's sandbox has consistently blocked all `WebFetch` calls to external sites — even neutral test domains like wikipedia.org — via a network egress policy, despite the prompt instructing it to verify facts via `WebFetch` on official event pages. In practice it falls back to cross-referencing multiple independent `WebSearch` results instead. Every synced report should be read as "search-corroborated," not "primary-source-verified," until this is resolved (if it ever can be, since it appears to be an environment-level restriction rather than a prompt issue).
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "TXproducts Conference Scout",
|
||||
"cron_expression": "0 6 * * 1",
|
||||
"cron_expression_human": "Every Monday 06:00 UTC (08:00 Europe/Berlin)",
|
||||
"enabled": true,
|
||||
"job_config": {
|
||||
"ccr": {
|
||||
"environment_id": "env_01Bfoxwtqvau2Pxk87vxAWiH",
|
||||
"session_context": {
|
||||
"model": "claude-sonnet-5",
|
||||
"sources": [],
|
||||
"allowed_tools": ["WebSearch", "WebFetch"]
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"data": {
|
||||
"type": "user",
|
||||
"parent_tool_use_id": null,
|
||||
"session_id": "",
|
||||
"message": {
|
||||
"role": "user",
|
||||
"content": "You are a research scout for TXproducts, a company whose business is: (1) buying synchrotron beamtime and using a device called Wavegate to cut experiment time, then selling beamtime access and X-ray data services to research and industry customers — data originates at beamline facilities such as DESY Hamburg. TXproducts' current industry focus is: semiconductors and power electronics (especially gallium nitride/GaN and silicon carbide/SiC, and other wide-bandgap semiconductor hardware), X-ray optics, and photon science / nanoscience, plus materials science more broadly. TXproducts actually attends events like SPIE Optics + Photonics (San Diego, USA) and SEMICON Europa (Munich) — use those as a sense of the right level and type of event. (2) TXproducts also builds scientific data infrastructure: a custom HDF5 spec (TXdata) for beamline XRD + Image data.\n\nTXproducts is ALSO actively looking to expand into new business — specifically defense and aerospace applications, wherever power electronics (GaN/SiC), semiconductor hardware, or X-ray/materials characterization intersects with defense or aerospace needs (e.g. radar, avionics, space hardware, RF power devices). This is prospecting for potential new business, not an established customer base yet — include it as a distinct category and flag it as such rather than implying TXproducts already has defense/aerospace customers.\n\nGEOGRAPHIC SCOPE: Search worldwide, not just Europe. Travel is not a constraint — actively include events in the US, China, other parts of Asia (Japan, South Korea, Taiwan, Singapore), and Africa, alongside Europe. Do not default to only European/DESY-adjacent events just because that's the home base — e.g. explicitly check for things like APEC, PCIM Asia, SEMICON China/Korea/Taiwan/Japan, CS International, ISPSD, IEDM, and any major power-electronics/GaN/SiC/semiconductor events in Asia or the Americas, plus any relevant events on the African continent.\n\nDo NOT include: pharma, drug discovery, or structural biology events (not a current focus); and do NOT include semantic-web / ontology / knowledge-graph / controlled-vocabulary conferences — that is an unrelated personal side project of one team member, not TXproducts business, and should never factor into this search.\n\nYour job: search the web for upcoming events relevant to this business (including the defense/aerospace expansion prospecting), across ALL relevant regions worldwide, and produce a report of candidates. Cast a wide net on FORMAT, not just headline trade shows — include large conferences and exhibitions, smaller technical seminars/workshops, industry-association networking events and mixers, and local/regional meetups, as long as the topical fit is genuine. Do NOT add anything to any file, calendar, or repository — this is a research report only, delivered as your final message text.\n\nRelevant domains to search across:\n- Synchrotron / X-ray facility science (beamline user meetings e.g. DESY, ESRF, Diamond, APS, SPring-8, SSRF; X-ray characterization methods, XRD, imaging)\n- Semiconductors and power electronics — especially gallium nitride (GaN), silicon carbide (SiC), and other wide-bandgap semiconductor hardware\n- X-ray optics (sources, components, instrumentation)\n- Photon science and nanoscience\n- Materials science conferences with a strong X-ray/characterization component\n- Defense and aerospace electronics — where power electronics (GaN/SiC), semiconductor hardware, or X-ray/materials characterization applies to defense or aerospace use cases (radar, avionics, space hardware, RF power devices); mark these clearly as \"potential new business\" rather than an existing customer relationship\n- Scientific data management, FAIR data, research data infrastructure (e.g. HDF5-based formats)\n- Semiconductor metrology / advanced packaging, particularly where it connects to power electronics, GaN, or SiC — general semiconductor manufacturing/fab trade shows are in scope only if they have a clear X-ray-metrology, GaN/SiC/power-electronics, or scientific-data angle\n\nInclude a mix of event formats where they exist: major conferences/trade shows, technical seminars/workshops, and industry networking events/meetups (e.g. regional industry-association mixers, chamber-of-commerce or trade-group networking nights in the relevant sectors). Note the format type for each entry, and note the region/country clearly.\n\nFor each candidate event, verify basic facts (dates, location, official website) using WebFetch on the event's own site before including it — do not rely on search snippets alone. Prefer events with confirmed, concrete dates over vague or unconfirmed ones.\n\nOutput a report with one entry per event, each containing:\n- Title\n- Format (conference / trade show / seminar / workshop / networking event)\n- Dates\n- Location (city, country)\n- Official website URL\n- One-line reason it's relevant to TXproductsLine truncated
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"_live_reference": {
|
||||
"note": "This block is a snapshot for humans, not consumed by the API. It is NOT kept in sync automatically — update it manually whenever the live routine is changed.",
|
||||
"trigger_id": "trig_01XRb81y9pAWDube5kjRUvGT",
|
||||
"routine_url": "https://claude.ai/code/routines/trig_01XRb81y9pAWDube5kjRUvGT",
|
||||
"last_synced_from_live": "2026-08-17"
|
||||
}
|
||||
}
|
||||
+25
-456
@@ -4,6 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>TXproducts Sales & Marketing</title>
|
||||
<link rel="stylesheet" href="opportunities/opportunities.css">
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; }
|
||||
@@ -728,86 +729,6 @@
|
||||
background: #f0f4f8; border: 1px solid #e0e7ef; border-radius: 6px; padding: 4px 9px;
|
||||
}
|
||||
.links a:hover { border-color: #2d6a9f; }
|
||||
|
||||
/* ---------- Opportunities (calendar) ---------- */
|
||||
.opp-toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; }
|
||||
.opp-month-label { font-size: 1.15rem; font-weight: 800; color: #1b3a5c; }
|
||||
.opp-nav-btn {
|
||||
font-family: inherit; font-size: 0.85rem; font-weight: 700; color: #2d6a9f;
|
||||
background: #fff; border: 1px solid #e0e7ef; border-radius: 6px; padding: 6px 12px; cursor: pointer;
|
||||
}
|
||||
.opp-nav-btn:hover { background: #e8f0fe; }
|
||||
.opp-today-btn { margin-left: 8px; }
|
||||
.opp-cal-grid {
|
||||
border-top: 1px solid #e0e7ef; border-left: 1px solid #e0e7ef;
|
||||
}
|
||||
.opp-cal-weekdays, .opp-cal-week {
|
||||
display: grid; grid-template-columns: repeat(7, 1fr);
|
||||
}
|
||||
.opp-cal-week { position: relative; }
|
||||
.opp-cal-weekday {
|
||||
text-align: center; font-size: 0.72rem; font-weight: 700; text-transform: uppercase;
|
||||
letter-spacing: 0.04em; color: #6b7a8d; padding: 8px 0;
|
||||
border-right: 1px solid #e0e7ef; border-bottom: 1px solid #e0e7ef; background: #f7f9fc;
|
||||
}
|
||||
.opp-cal-cell {
|
||||
min-height: 96px; border-right: 1px solid #e0e7ef; border-bottom: 1px solid #e0e7ef;
|
||||
padding: 8px;
|
||||
}
|
||||
.opp-cal-cell.outside { background: #fafbfc; }
|
||||
.opp-cal-cell.outside .opp-cal-daynum { color: #c3cdd8; }
|
||||
.opp-cal-daynum { font-size: 0.82rem; font-weight: 700; color: #1a2533; }
|
||||
.opp-cal-cell.today .opp-cal-daynum {
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
width: 22px; height: 22px; border-radius: 50%; background: #2d6a9f; color: #fff;
|
||||
}
|
||||
.opp-cal-event-bar {
|
||||
position: absolute; height: 20px; line-height: 20px; border-radius: 4px;
|
||||
background: #2d6a9f; color: #fff; font-size: 0.72rem; font-weight: 700;
|
||||
padding: 0 8px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
cursor: default;
|
||||
}
|
||||
.opp-cal-event-bar .opp-cal-event-title { color: #ffd43b; font-weight: 800; text-decoration: none; }
|
||||
a.opp-cal-event-title:hover { text-decoration: underline; }
|
||||
.opp-cal-event-bar .opp-cal-event-loc { color: #fff; font-weight: 600; opacity: 0.82; margin-left: 6px; }
|
||||
.opp-cal-event-bar.continues-left { border-top-left-radius: 0; border-bottom-left-radius: 0; }
|
||||
.opp-cal-event-bar.continues-right { border-top-right-radius: 0; border-bottom-right-radius: 0; }
|
||||
|
||||
#tab-opportunities .drill-view { padding: 0; }
|
||||
.opp-layout { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; }
|
||||
@media (max-width: 900px) { .opp-layout { grid-template-columns: 1fr; } }
|
||||
.opp-list-card { padding: 24px; }
|
||||
.opp-list-heading { margin: 0 0 16px; font-size: 1.05rem; font-weight: 800; color: #1b3a5c; }
|
||||
.opp-list { display: flex; flex-direction: column; gap: 10px; }
|
||||
.opp-list-empty { color: #6b7a8d; font-size: 0.9rem; }
|
||||
.opp-list-row {
|
||||
display: flex; align-items: flex-start; gap: 12px; padding: 12px;
|
||||
border: 1px solid #e0e7ef; border-radius: 8px; cursor: pointer;
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
}
|
||||
.opp-list-row:hover { border-color: #2d6a9f; background: #f7f9fc; }
|
||||
.opp-list-meta {
|
||||
flex: 0 0 128px; display: flex; flex-direction: column; gap: 3px;
|
||||
font-size: 0.82rem; line-height: 1.5;
|
||||
}
|
||||
.opp-list-dates { font-weight: 700; color: #1a2533; }
|
||||
.opp-list-loc, .opp-list-attendee { color: #6b7a8d; }
|
||||
.opp-list-link {
|
||||
display: inline-block; margin-top: 3px; font-size: 0.78rem; font-weight: 700;
|
||||
color: #2d6a9f; text-decoration: underline;
|
||||
}
|
||||
.opp-list-about { flex: 1; border-left: 1px solid #eef1f5; padding-left: 16px; }
|
||||
.opp-scout-note {
|
||||
background: #fbf0df; border: 1px solid #f3ddb8; border-radius: 8px;
|
||||
padding: 12px 14px; margin-bottom: 16px; font-size: 0.82rem; line-height: 1.55; color: #7a4a06;
|
||||
}
|
||||
.opp-scout-note strong { color: #b45f06; }
|
||||
.opp-list-badge {
|
||||
display: inline-block; background: #fbf0df; color: #b45f06; font-weight: 800;
|
||||
font-size: 0.78rem; border-radius: 20px; padding: 5px 12px; text-decoration: none; white-space: nowrap;
|
||||
}
|
||||
a.opp-list-badge:hover { text-decoration: underline; }
|
||||
.opp-list-abstract { margin: 8px 0 0; font-size: 0.82rem; line-height: 1.55; color: #3d4c5c; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -4562,43 +4483,7 @@
|
||||
</div>
|
||||
|
||||
<div id="tab-opportunities" class="tab-content">
|
||||
<div class="drill-crumbs" id="opp-crumbs" style="margin-bottom: 14px;"></div>
|
||||
|
||||
<div class="drill-view active" data-view="opportunities-root">
|
||||
<div class="opp-layout">
|
||||
<div class="card opp-list-card">
|
||||
<h3 class="opp-list-heading">Events</h3>
|
||||
<p class="narrative" style="max-width: none;">Conferences, events, and potential opportunities for TXproducts.</p>
|
||||
<div class="opp-list" id="opp-event-list"></div>
|
||||
</div>
|
||||
<div class="card opp-cal-card" style="padding: 0;">
|
||||
<div style="padding: 24px 32px 28px;">
|
||||
<div class="opp-toolbar">
|
||||
<div class="opp-month-label" id="opp-month-label"></div>
|
||||
<div>
|
||||
<button class="opp-nav-btn" id="opp-prev-btn">← Prev</button>
|
||||
<button class="opp-nav-btn opp-today-btn" id="opp-today-btn">Today</button>
|
||||
<button class="opp-nav-btn" id="opp-next-btn">Next →</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="opp-cal-grid" id="opp-cal-grid"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="drill-view" data-view="scout">
|
||||
<div class="card">
|
||||
<h2>Conference Scout</h2>
|
||||
<p class="narrative" style="max-width: none;">A scheduled research agent that sweeps the web every Monday morning for conferences, trade shows, seminars, and networking events relevant to TXproducts — semiconductors, power electronics (GaN/SiC), X-ray optics, photon/nanoscience, and defense/aerospace expansion prospecting. Review the candidates below, then add anything worth tracking to the Events list yourself — nothing here is added automatically.</p>
|
||||
<div class="opp-scout-note"><strong>Verification note:</strong> this run's sandbox blocked direct fetches to every official event site (even neutral test domains), so dates and locations below are cross-referenced across multiple independent search sources rather than confirmed on the primary page — double-check the official site before committing budget or travel. This is a fresh worldwide sweep (12 candidates, replacing the previous Europe-only list) spanning Asia, the Americas, Africa, and Europe, tagged "Existing focus area" or "Potential new business (defense)" per TXproducts' corrected scope: semiconductors/power electronics (GaN/SiC), X-ray optics, photon/nanoscience, synchrotron/beamline science, and defense/aerospace prospecting.</div>
|
||||
<div class="opp-list" id="opp-scout-list"></div>
|
||||
<div class="drill-actions" style="margin-top: 18px;">
|
||||
<button class="drill-back" data-target="opportunities-root">← Opportunities</button>
|
||||
<a class="open-link" href="https://claude.ai/code/routines/trig_01XRb81y9pAWDube5kjRUvGT" target="_blank" rel="noopener">Open Conference Scout ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="opp-loading">Loading…</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4629,8 +4514,8 @@
|
||||
|
||||
/* ---- Drill-downs: Marketing -> Social Media (dropdown) -> LinkedIn -> Dash
|
||||
Sales -> Market Intelligence -> site (goes there directly, one child today) ---- */
|
||||
var HERO_NAV_BY_TAB = { marketing: ["hero-nav-social", "hero-nav-conferences"], opportunities: ["hero-nav-scout"] };
|
||||
var DRILL_DEFAULT_VIEW = { marketing: "marketing-root", sales: "market-intelligence", opportunities: "opportunities-root" };
|
||||
var HERO_NAV_BY_TAB = { marketing: ["hero-nav-social", "hero-nav-conferences"] };
|
||||
var DRILL_DEFAULT_VIEW = { marketing: "marketing-root", sales: "market-intelligence" };
|
||||
var DRILL_CONFIG = {
|
||||
marketing: {
|
||||
crumbsEl: "mkt-crumbs",
|
||||
@@ -4646,22 +4531,20 @@
|
||||
"sales-root": [["Sales", "sales-root"]],
|
||||
"market-intelligence": [["Sales", "sales-root"], ["Intelligence Board", "market-intelligence"], ["Market Intelligence", "market-intelligence"]]
|
||||
}
|
||||
},
|
||||
opportunities: {
|
||||
crumbsEl: "opp-crumbs",
|
||||
crumbs: {
|
||||
"opportunities-root": [["Opportunities", "opportunities-root"]],
|
||||
"scout": [["Opportunities", "opportunities-root"], ["Conference Scout", "scout"]]
|
||||
}
|
||||
}
|
||||
};
|
||||
/* Feature modules loaded async (see opportunities/opportunities.js) register their own
|
||||
HERO_NAV_BY_TAB / DRILL_DEFAULT_VIEW / DRILL_CONFIG entries once their partial HTML
|
||||
is injected — setDrillView tolerates scopes that aren't registered yet. */
|
||||
function setDrillView(scope, view) {
|
||||
var cfg = DRILL_CONFIG[scope];
|
||||
if (!cfg) return;
|
||||
document.querySelectorAll("#tab-" + scope + " .drill-view").forEach(function (v) {
|
||||
v.classList.toggle("active", v.getAttribute("data-view") === view);
|
||||
});
|
||||
var cfg = DRILL_CONFIG[scope];
|
||||
var crumbTrail = cfg.crumbs[view];
|
||||
var crumbsEl = document.getElementById(cfg.crumbsEl);
|
||||
if (!crumbsEl) return;
|
||||
crumbsEl.innerHTML = "";
|
||||
crumbTrail.forEach(function (pair, i) {
|
||||
if (i > 0) {
|
||||
@@ -4680,7 +4563,7 @@
|
||||
crumbsEl.appendChild(el);
|
||||
});
|
||||
}
|
||||
["marketing", "sales", "opportunities"].forEach(function (scope) {
|
||||
["marketing", "sales"].forEach(function (scope) {
|
||||
document.querySelectorAll("#tab-" + scope + " .drill-back, #tab-" + scope + " .drill-next").forEach(function (b) {
|
||||
b.addEventListener("click", function () { setDrillView(scope, b.getAttribute("data-target")); });
|
||||
});
|
||||
@@ -4708,9 +4591,6 @@
|
||||
document.getElementById("hero-nav-conferences-btn").addEventListener("click", function () {
|
||||
setDrillView("marketing", "conferences");
|
||||
});
|
||||
document.getElementById("hero-nav-scout-btn").addEventListener("click", function () {
|
||||
setDrillView("opportunities", "scout");
|
||||
});
|
||||
|
||||
/* ---- Conferences (Directory pattern: conference cards on the left, drill into
|
||||
its own presentation sub-directory on the right) ---- */
|
||||
@@ -6006,332 +5886,21 @@
|
||||
document.getElementById("ab-view-board-btn").click();
|
||||
document.querySelector(".ab-filter-chip[data-source=\"Outreach\"]").click();
|
||||
|
||||
/* ---------- Opportunities calendar ----------
|
||||
Each entry: { start: "YYYY-MM-DD", end: "YYYY-MM-DD" (defaults to start),
|
||||
title, location, attendee (optional) }.
|
||||
Multi-day entries render as one bar spanning their full date range;
|
||||
bars continuing past a week boundary lose the rounded corner on that side. */
|
||||
var OPPORTUNITIES = [
|
||||
{ start: "2026-08-23", end: "2026-08-27", title: "SPIE", location: "San Diego, CA", attendee: "Peter", url: "https://spie.org/conferences-and-exhibitions/optics-and-photonics",
|
||||
abstract: "SPIE's flagship annual optics and photonics conference and exhibition, held every August in San Diego since the 1960s. Brings together 10,000+ scientists, engineers, and industry professionals across optical engineering, nanoscience, quantum science, and organic photonics." },
|
||||
{ start: "2026-11-10", end: "2026-11-13", title: "SEMICON Europa", location: "Munich, Germany", url: "https://www.semiconeuropa.org/",
|
||||
abstract: "Semiconductor and electronics manufacturing trade show co-located with electronica in Munich. Covers fab management, advanced packaging, and smart manufacturing across the chip supply chain, drawing 8,000+ visitors from equipment vendors, fabs, and OSATs." }
|
||||
];
|
||||
var MONTH_NAMES = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
||||
var MONTH_ABBR = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||
var oppCursor = new Date();
|
||||
oppCursor.setDate(1);
|
||||
function oppKey(d) {
|
||||
return d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0");
|
||||
}
|
||||
function parseOppDate(s) {
|
||||
var parts = s.split("-").map(Number);
|
||||
return new Date(parts[0], parts[1] - 1, parts[2]);
|
||||
}
|
||||
function addOppDays(d, n) {
|
||||
var r = new Date(d);
|
||||
r.setDate(r.getDate() + n);
|
||||
return r;
|
||||
}
|
||||
function buildOppEvents() {
|
||||
return OPPORTUNITIES.map(function (o) {
|
||||
return {
|
||||
title: o.title, location: o.location, attendee: o.attendee, url: o.url, abstract: o.abstract,
|
||||
start: parseOppDate(o.start), end: parseOppDate(o.end || o.start)
|
||||
};
|
||||
}).sort(function (a, b) { return a.start - b.start; });
|
||||
}
|
||||
function formatOppDateRange(start, end) {
|
||||
if (start.getTime() === end.getTime()) {
|
||||
return MONTH_ABBR[start.getMonth()] + " " + start.getDate() + ", " + start.getFullYear();
|
||||
}
|
||||
if (start.getFullYear() === end.getFullYear()) {
|
||||
if (start.getMonth() === end.getMonth()) {
|
||||
return MONTH_ABBR[start.getMonth()] + " " + start.getDate() + "–" + end.getDate() + ", " + start.getFullYear();
|
||||
}
|
||||
return MONTH_ABBR[start.getMonth()] + " " + start.getDate() + " – " + MONTH_ABBR[end.getMonth()] + " " + end.getDate() + ", " + start.getFullYear();
|
||||
}
|
||||
return MONTH_ABBR[start.getMonth()] + " " + start.getDate() + ", " + start.getFullYear() + " – " + MONTH_ABBR[end.getMonth()] + " " + end.getDate() + ", " + end.getFullYear();
|
||||
}
|
||||
function renderOppEventList() {
|
||||
var listEl = document.getElementById("opp-event-list");
|
||||
if (!listEl) return;
|
||||
listEl.innerHTML = "";
|
||||
var events = buildOppEvents();
|
||||
if (!events.length) {
|
||||
var empty = document.createElement("div");
|
||||
empty.className = "opp-list-empty";
|
||||
empty.textContent = "No events yet.";
|
||||
listEl.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
events.forEach(function (e) {
|
||||
var row = document.createElement("div");
|
||||
row.className = "opp-list-row";
|
||||
|
||||
var meta = document.createElement("div");
|
||||
meta.className = "opp-list-meta";
|
||||
var datesEl = document.createElement("div");
|
||||
datesEl.className = "opp-list-dates";
|
||||
datesEl.textContent = formatOppDateRange(e.start, e.end);
|
||||
meta.appendChild(datesEl);
|
||||
if (e.location) {
|
||||
var locEl = document.createElement("div");
|
||||
locEl.className = "opp-list-loc";
|
||||
locEl.textContent = e.location;
|
||||
meta.appendChild(locEl);
|
||||
}
|
||||
if (e.attendee) {
|
||||
var attEl = document.createElement("div");
|
||||
attEl.className = "opp-list-attendee";
|
||||
attEl.textContent = "Attending: " + e.attendee;
|
||||
meta.appendChild(attEl);
|
||||
}
|
||||
if (e.url) {
|
||||
var linkEl = document.createElement("a");
|
||||
linkEl.className = "opp-list-link";
|
||||
linkEl.href = e.url;
|
||||
linkEl.target = "_blank";
|
||||
linkEl.rel = "noopener";
|
||||
linkEl.textContent = "Website";
|
||||
meta.appendChild(linkEl);
|
||||
}
|
||||
row.appendChild(meta);
|
||||
|
||||
var about = document.createElement("div");
|
||||
about.className = "opp-list-about";
|
||||
var badge = document.createElement(e.url ? "a" : "span");
|
||||
badge.className = "opp-list-badge";
|
||||
badge.textContent = e.title;
|
||||
if (e.url) {
|
||||
badge.href = e.url;
|
||||
badge.target = "_blank";
|
||||
badge.rel = "noopener";
|
||||
}
|
||||
about.appendChild(badge);
|
||||
if (e.abstract) {
|
||||
var abstractEl = document.createElement("p");
|
||||
abstractEl.className = "opp-list-abstract";
|
||||
abstractEl.textContent = e.abstract;
|
||||
about.appendChild(abstractEl);
|
||||
}
|
||||
row.appendChild(about);
|
||||
|
||||
row.addEventListener("click", function () {
|
||||
oppCursor = new Date(e.start.getFullYear(), e.start.getMonth(), 1);
|
||||
renderOppCalendar();
|
||||
});
|
||||
listEl.appendChild(row);
|
||||
/* ---- Opportunities module loader ----
|
||||
Fetches its markup, injects it into the empty tab container, then loads its
|
||||
script as a normal <script src> (so it runs only once the DOM it needs exists). */
|
||||
fetch("opportunities/opportunities.html")
|
||||
.then(function (r) { return r.text(); })
|
||||
.then(function (html) {
|
||||
document.getElementById("tab-opportunities").innerHTML = html;
|
||||
var s = document.createElement("script");
|
||||
s.src = "opportunities/opportunities.js";
|
||||
document.body.appendChild(s);
|
||||
})
|
||||
.catch(function (err) {
|
||||
document.getElementById("tab-opportunities").innerHTML =
|
||||
"<div class=\"opp-loading\">Couldn't load Opportunities (" + err + ").</div>";
|
||||
});
|
||||
}
|
||||
function renderOppCalendar() {
|
||||
var label = document.getElementById("opp-month-label");
|
||||
var grid = document.getElementById("opp-cal-grid");
|
||||
if (!label || !grid) return;
|
||||
label.textContent = MONTH_NAMES[oppCursor.getMonth()] + " " + oppCursor.getFullYear();
|
||||
grid.innerHTML = "";
|
||||
|
||||
var weekdayRow = document.createElement("div");
|
||||
weekdayRow.className = "opp-cal-weekdays";
|
||||
["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].forEach(function (wd) {
|
||||
var wdEl = document.createElement("div");
|
||||
wdEl.className = "opp-cal-weekday";
|
||||
wdEl.textContent = wd;
|
||||
weekdayRow.appendChild(wdEl);
|
||||
});
|
||||
grid.appendChild(weekdayRow);
|
||||
|
||||
var year = oppCursor.getFullYear(), month = oppCursor.getMonth();
|
||||
var firstDow = new Date(year, month, 1).getDay();
|
||||
var daysInMonth = new Date(year, month + 1, 0).getDate();
|
||||
var totalCells = Math.ceil((firstDow + daysInMonth) / 7) * 7;
|
||||
var gridStart = new Date(year, month, 1 - firstDow);
|
||||
var todayKey = oppKey(new Date());
|
||||
|
||||
var events = buildOppEvents();
|
||||
|
||||
for (var w = 0; w < totalCells / 7; w++) {
|
||||
var weekStart = addOppDays(gridStart, w * 7);
|
||||
var weekEnd = addOppDays(weekStart, 6);
|
||||
var weekEl = document.createElement("div");
|
||||
weekEl.className = "opp-cal-week";
|
||||
|
||||
var weekEvents = events
|
||||
.filter(function (e) { return e.start <= weekEnd && e.end >= weekStart; })
|
||||
.sort(function (a, b) { return a.start - b.start; });
|
||||
var lanes = [];
|
||||
weekEvents.forEach(function (e) {
|
||||
var colStart = Math.max(0, Math.round((Math.max(e.start, weekStart) - weekStart) / 86400000));
|
||||
var colEnd = Math.min(6, Math.round((Math.min(e.end, weekEnd) - weekStart) / 86400000));
|
||||
var lane = 0;
|
||||
while (lanes[lane] && lanes[lane].some(function (r) { return !(colEnd < r[0] || colStart > r[1]); })) lane++;
|
||||
if (!lanes[lane]) lanes[lane] = [];
|
||||
lanes[lane].push([colStart, colEnd]);
|
||||
e._col = [colStart, colEnd];
|
||||
e._lane = lane;
|
||||
});
|
||||
var cellMinHeight = Math.max(96, 34 + lanes.length * 24 + 10);
|
||||
|
||||
for (var d = 0; d < 7; d++) {
|
||||
var cellDate = addOppDays(weekStart, d);
|
||||
var outside = cellDate.getMonth() !== month;
|
||||
var key = oppKey(cellDate);
|
||||
var cell = document.createElement("div");
|
||||
cell.className = "opp-cal-cell" + (outside ? " outside" : "") + (key === todayKey ? " today" : "");
|
||||
cell.style.minHeight = cellMinHeight + "px";
|
||||
var numEl = document.createElement("div");
|
||||
numEl.className = "opp-cal-daynum";
|
||||
numEl.textContent = cellDate.getDate();
|
||||
cell.appendChild(numEl);
|
||||
weekEl.appendChild(cell);
|
||||
}
|
||||
|
||||
weekEvents.forEach(function (e) {
|
||||
var bar = document.createElement("div");
|
||||
bar.className = "opp-cal-event-bar";
|
||||
if (e.start < weekStart) bar.classList.add("continues-left");
|
||||
if (e.end > weekEnd) bar.classList.add("continues-right");
|
||||
bar.style.left = "calc(" + (e._col[0] / 7 * 100) + "% + 4px)";
|
||||
bar.style.width = "calc(" + ((e._col[1] - e._col[0] + 1) / 7 * 100) + "% - 8px)";
|
||||
bar.style.top = (34 + e._lane * 24) + "px";
|
||||
var titleEl = document.createElement(e.url ? "a" : "span");
|
||||
titleEl.className = "opp-cal-event-title";
|
||||
titleEl.textContent = e.title;
|
||||
if (e.url) {
|
||||
titleEl.href = e.url;
|
||||
titleEl.target = "_blank";
|
||||
titleEl.rel = "noopener";
|
||||
}
|
||||
bar.appendChild(titleEl);
|
||||
if (e.location) {
|
||||
var locEl = document.createElement("span");
|
||||
locEl.className = "opp-cal-event-loc";
|
||||
locEl.textContent = e.location;
|
||||
bar.appendChild(locEl);
|
||||
}
|
||||
bar.title = e.title + (e.location ? " — " + e.location : "") + (e.attendee ? " (" + e.attendee + ")" : "");
|
||||
weekEl.appendChild(bar);
|
||||
});
|
||||
|
||||
grid.appendChild(weekEl);
|
||||
}
|
||||
}
|
||||
/* ---------- Conference Scout results ----------
|
||||
Snapshot of the weekly research agent's report, hand-reviewed and pasted in here.
|
||||
Each entry: { title, dates (display text), location, url, reason }. */
|
||||
var SCOUT_RESULTS = [
|
||||
{ title: "PCIM Asia 2026", dates: "Aug 26–28, 2026", location: "Shenzhen, China",
|
||||
reason: "Asia's leading power electronics / wide-bandgap semiconductor trade show — direct GaN/SiC device and materials audience. Existing focus area." },
|
||||
{ title: "IMAPS Onshoring Workshop 2026", dates: "Aug 31–Sept 2, 2026", location: "Arlington, VA, USA",
|
||||
url: "https://imaps.org/page/Onshoring",
|
||||
reason: "Workshop on the US Defense Industrial Base's microelectronics packaging/assembly onshoring — a direct entry point for X-ray/semiconductor characterization into defense supply chains. Potential new business (defense)." },
|
||||
{ title: "SEMICON Taiwan 2026", dates: "Sept 2–4, 2026", location: "Taipei, Taiwan",
|
||||
url: "https://www.semicontaiwan.org/",
|
||||
reason: "Major semiconductor equipment/materials/metrology trade show in a top compound-semiconductor manufacturing hub. Existing focus area." },
|
||||
{ title: "WiPDA-Europe 2026", dates: "Sept 20–22, 2026", location: "Aalborg, Denmark",
|
||||
url: "https://www.energy.aau.dk/wipda-europe-2026-e156452",
|
||||
reason: "IEEE workshop on wide-bandgap (GaN/SiC) power devices — niche but highly targeted technical audience. Existing focus area." },
|
||||
{ title: "XTOP 2026", dates: "Sept 21–25, 2026", location: "Karlsruhe, Germany",
|
||||
url: "https://xtop2026.kit.edu",
|
||||
reason: "16th Biennial Conference on High-Resolution X-Ray Diffraction and Imaging — direct X-ray diffraction/imaging overlap. Existing focus area." },
|
||||
{ title: "NOBUGS 2026", dates: "Sept 21–25, 2026 (core Sept 22–24)", location: "Schenefeld Campus (European XFEL), near Hamburg, Germany",
|
||||
url: "https://nobugs2026.xfel.eu/",
|
||||
reason: "Explicitly covers metadata/data formats and FAIR data management for beamline software; co-hosted by DESY/European XFEL on TXproducts' home campus — the single best topical fit found. Existing focus area." },
|
||||
{ title: "ICSCRM 2026", dates: "Sept 27–Oct 2, 2026", location: "Yokohama, Japan",
|
||||
reason: "Premier international conference on silicon carbide and related materials — direct match for the SiC/X-ray metrology work. Existing focus area." },
|
||||
{ title: "RDA 27th Plenary Meeting (P27)", dates: "Oct 6–8, 2026", location: "London, UK",
|
||||
url: "https://www.rd-alliance.org/plenaries/p27/",
|
||||
reason: "Research Data Alliance plenary — strong FAIR/research-data-infrastructure networking, though less domain-specific than NOBUGS. Existing focus area." },
|
||||
{ title: "AFLS2026", dates: "Nov 16–20, 2026", location: "Harare, Zimbabwe",
|
||||
url: "https://events.saip.org.za/event/255",
|
||||
reason: "African Light Source / African Physical Society joint meeting — the Africa-based synchrotron-development community, with materials/X-ray characterization topics. Existing focus area." },
|
||||
{ title: "MRS Fall Meeting & Exhibit 2026", dates: "Nov 29–Dec 4, 2026", location: "Boston, MA, USA",
|
||||
url: "https://www.mrs.org/",
|
||||
reason: "Broad materials-science conference with strong X-ray characterization tracks. Existing focus area." },
|
||||
{ title: "GOMACTech 2027", dates: "March 22–25, 2027", location: "Pasadena, CA, USA",
|
||||
url: "https://www.gomactech.net/",
|
||||
reason: "The premier US DoD microelectronics conference — covers GaN/SiC power devices and X-ray characterization/reliability for defense microelectronics. Potential new business (defense)." },
|
||||
{ title: "IEEE RadarConf'27", dates: "May 1–6, 2027", location: "Bangalore, India",
|
||||
url: "https://radarconf2027.org/",
|
||||
reason: "Radar hardware relies heavily on GaN/SiC RF power amplifiers — a defense-radar audience in a growing Indian defense-electronics market. Potential new business (defense)." }
|
||||
];
|
||||
function renderOppScoutList() {
|
||||
var listEl = document.getElementById("opp-scout-list");
|
||||
if (!listEl) return;
|
||||
listEl.innerHTML = "";
|
||||
if (!SCOUT_RESULTS.length) {
|
||||
var empty = document.createElement("div");
|
||||
empty.className = "opp-list-empty";
|
||||
empty.textContent = "No results yet.";
|
||||
listEl.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
SCOUT_RESULTS.forEach(function (s) {
|
||||
var row = document.createElement("div");
|
||||
row.className = "opp-list-row";
|
||||
row.style.cursor = "default";
|
||||
|
||||
var meta = document.createElement("div");
|
||||
meta.className = "opp-list-meta";
|
||||
var datesEl = document.createElement("div");
|
||||
datesEl.className = "opp-list-dates";
|
||||
datesEl.textContent = s.dates;
|
||||
meta.appendChild(datesEl);
|
||||
if (s.location) {
|
||||
var locEl = document.createElement("div");
|
||||
locEl.className = "opp-list-loc";
|
||||
locEl.textContent = s.location;
|
||||
meta.appendChild(locEl);
|
||||
}
|
||||
if (s.url) {
|
||||
var linkEl = document.createElement("a");
|
||||
linkEl.className = "opp-list-link";
|
||||
linkEl.href = s.url;
|
||||
linkEl.target = "_blank";
|
||||
linkEl.rel = "noopener";
|
||||
linkEl.textContent = "Website";
|
||||
meta.appendChild(linkEl);
|
||||
}
|
||||
row.appendChild(meta);
|
||||
|
||||
var about = document.createElement("div");
|
||||
about.className = "opp-list-about";
|
||||
var badge = document.createElement(s.url ? "a" : "span");
|
||||
badge.className = "opp-list-badge";
|
||||
badge.textContent = s.title;
|
||||
if (s.url) {
|
||||
badge.href = s.url;
|
||||
badge.target = "_blank";
|
||||
badge.rel = "noopener";
|
||||
}
|
||||
about.appendChild(badge);
|
||||
if (s.reason) {
|
||||
var reasonEl = document.createElement("p");
|
||||
reasonEl.className = "opp-list-abstract";
|
||||
reasonEl.textContent = s.reason;
|
||||
about.appendChild(reasonEl);
|
||||
}
|
||||
row.appendChild(about);
|
||||
|
||||
listEl.appendChild(row);
|
||||
});
|
||||
}
|
||||
document.getElementById("opp-prev-btn").addEventListener("click", function () {
|
||||
oppCursor.setMonth(oppCursor.getMonth() - 1);
|
||||
renderOppCalendar();
|
||||
});
|
||||
document.getElementById("opp-next-btn").addEventListener("click", function () {
|
||||
oppCursor.setMonth(oppCursor.getMonth() + 1);
|
||||
renderOppCalendar();
|
||||
});
|
||||
document.getElementById("opp-today-btn").addEventListener("click", function () {
|
||||
oppCursor = new Date();
|
||||
oppCursor.setDate(1);
|
||||
renderOppCalendar();
|
||||
});
|
||||
renderOppEventList();
|
||||
renderOppCalendar();
|
||||
renderOppScoutList();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,80 @@
|
||||
/* ---------- Opportunities (calendar) ---------- */
|
||||
.opp-loading { color: #9aa7b4; font-size: 0.9rem; padding: 40px 0; text-align: center; }
|
||||
.opp-toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; }
|
||||
.opp-month-label { font-size: 1.15rem; font-weight: 800; color: #1b3a5c; }
|
||||
.opp-nav-btn {
|
||||
font-family: inherit; font-size: 0.85rem; font-weight: 700; color: #2d6a9f;
|
||||
background: #fff; border: 1px solid #e0e7ef; border-radius: 6px; padding: 6px 12px; cursor: pointer;
|
||||
}
|
||||
.opp-nav-btn:hover { background: #e8f0fe; }
|
||||
.opp-today-btn { margin-left: 8px; }
|
||||
.opp-cal-grid {
|
||||
border-top: 1px solid #e0e7ef; border-left: 1px solid #e0e7ef;
|
||||
}
|
||||
.opp-cal-weekdays, .opp-cal-week {
|
||||
display: grid; grid-template-columns: repeat(7, 1fr);
|
||||
}
|
||||
.opp-cal-week { position: relative; }
|
||||
.opp-cal-weekday {
|
||||
text-align: center; font-size: 0.72rem; font-weight: 700; text-transform: uppercase;
|
||||
letter-spacing: 0.04em; color: #6b7a8d; padding: 8px 0;
|
||||
border-right: 1px solid #e0e7ef; border-bottom: 1px solid #e0e7ef; background: #f7f9fc;
|
||||
}
|
||||
.opp-cal-cell {
|
||||
min-height: 96px; border-right: 1px solid #e0e7ef; border-bottom: 1px solid #e0e7ef;
|
||||
padding: 8px;
|
||||
}
|
||||
.opp-cal-cell.outside { background: #fafbfc; }
|
||||
.opp-cal-cell.outside .opp-cal-daynum { color: #c3cdd8; }
|
||||
.opp-cal-daynum { font-size: 0.82rem; font-weight: 700; color: #1a2533; }
|
||||
.opp-cal-cell.today .opp-cal-daynum {
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
width: 22px; height: 22px; border-radius: 50%; background: #2d6a9f; color: #fff;
|
||||
}
|
||||
.opp-cal-event-bar {
|
||||
position: absolute; height: 20px; line-height: 20px; border-radius: 4px;
|
||||
background: #2d6a9f; color: #fff; font-size: 0.72rem; font-weight: 700;
|
||||
padding: 0 8px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
cursor: default;
|
||||
}
|
||||
.opp-cal-event-bar .opp-cal-event-title { color: #ffd43b; font-weight: 800; text-decoration: none; }
|
||||
a.opp-cal-event-title:hover { text-decoration: underline; }
|
||||
.opp-cal-event-bar .opp-cal-event-loc { color: #fff; font-weight: 600; opacity: 0.82; margin-left: 6px; }
|
||||
.opp-cal-event-bar.continues-left { border-top-left-radius: 0; border-bottom-left-radius: 0; }
|
||||
.opp-cal-event-bar.continues-right { border-top-right-radius: 0; border-bottom-right-radius: 0; }
|
||||
|
||||
#tab-opportunities .drill-view { padding: 0; }
|
||||
.opp-layout { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; }
|
||||
@media (max-width: 900px) { .opp-layout { grid-template-columns: 1fr; } }
|
||||
.opp-list-card { padding: 24px; }
|
||||
.opp-list-heading { margin: 0 0 16px; font-size: 1.05rem; font-weight: 800; color: #1b3a5c; }
|
||||
.opp-list { display: flex; flex-direction: column; gap: 10px; }
|
||||
.opp-list-empty { color: #6b7a8d; font-size: 0.9rem; }
|
||||
.opp-list-row {
|
||||
display: flex; align-items: flex-start; gap: 12px; padding: 12px;
|
||||
border: 1px solid #e0e7ef; border-radius: 8px; cursor: pointer;
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
}
|
||||
.opp-list-row:hover { border-color: #2d6a9f; background: #f7f9fc; }
|
||||
.opp-list-meta {
|
||||
flex: 0 0 128px; display: flex; flex-direction: column; gap: 3px;
|
||||
font-size: 0.82rem; line-height: 1.5;
|
||||
}
|
||||
.opp-list-dates { font-weight: 700; color: #1a2533; }
|
||||
.opp-list-loc, .opp-list-attendee { color: #6b7a8d; }
|
||||
.opp-list-link {
|
||||
display: inline-block; margin-top: 3px; font-size: 0.78rem; font-weight: 700;
|
||||
color: #2d6a9f; text-decoration: underline;
|
||||
}
|
||||
.opp-list-about { flex: 1; border-left: 1px solid #eef1f5; padding-left: 16px; }
|
||||
.opp-scout-note {
|
||||
background: #fbf0df; border: 1px solid #f3ddb8; border-radius: 8px;
|
||||
padding: 12px 14px; margin-bottom: 16px; font-size: 0.82rem; line-height: 1.55; color: #7a4a06;
|
||||
}
|
||||
.opp-scout-note strong { color: #b45f06; }
|
||||
.opp-list-badge {
|
||||
display: inline-block; background: #fbf0df; color: #b45f06; font-weight: 800;
|
||||
font-size: 0.78rem; border-radius: 20px; padding: 5px 12px; text-decoration: none; white-space: nowrap;
|
||||
}
|
||||
a.opp-list-badge:hover { text-decoration: underline; }
|
||||
.opp-list-abstract { margin: 8px 0 0; font-size: 0.82rem; line-height: 1.55; color: #3d4c5c; }
|
||||
@@ -0,0 +1,37 @@
|
||||
<div class="drill-crumbs" id="opp-crumbs" style="margin-bottom: 14px;"></div>
|
||||
|
||||
<div class="drill-view active" data-view="opportunities-root">
|
||||
<div class="opp-layout">
|
||||
<div class="card opp-list-card">
|
||||
<h3 class="opp-list-heading">Events</h3>
|
||||
<p class="narrative" style="max-width: none;">Conferences, events, and potential opportunities for TXproducts.</p>
|
||||
<div class="opp-list" id="opp-event-list"></div>
|
||||
</div>
|
||||
<div class="card opp-cal-card" style="padding: 0;">
|
||||
<div style="padding: 24px 32px 28px;">
|
||||
<div class="opp-toolbar">
|
||||
<div class="opp-month-label" id="opp-month-label"></div>
|
||||
<div>
|
||||
<button class="opp-nav-btn" id="opp-prev-btn">← Prev</button>
|
||||
<button class="opp-nav-btn opp-today-btn" id="opp-today-btn">Today</button>
|
||||
<button class="opp-nav-btn" id="opp-next-btn">Next →</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="opp-cal-grid" id="opp-cal-grid"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="drill-view" data-view="scout">
|
||||
<div class="card">
|
||||
<h2>Conference Scout</h2>
|
||||
<p class="narrative" style="max-width: none;">A scheduled research agent that sweeps the web every Monday morning for conferences, trade shows, seminars, and networking events relevant to TXproducts — semiconductors, power electronics (GaN/SiC), X-ray optics, photon/nanoscience, and defense/aerospace expansion prospecting. Review the candidates below, then add anything worth tracking to the Events list yourself — nothing here is added automatically.</p>
|
||||
<div class="opp-scout-note"><strong>Verification note:</strong> this run's sandbox blocked direct fetches to every official event site (even neutral test domains), so dates and locations below are cross-referenced across multiple independent search sources rather than confirmed on the primary page — double-check the official site before committing budget or travel. This is a fresh worldwide sweep (12 candidates, replacing the previous Europe-only list) spanning Asia, the Americas, Africa, and Europe, tagged "Existing focus area" or "Potential new business (defense)" per TXproducts' corrected scope: semiconductors/power electronics (GaN/SiC), X-ray optics, photon/nanoscience, synchrotron/beamline science, and defense/aerospace prospecting.</div>
|
||||
<div class="opp-list" id="opp-scout-list"></div>
|
||||
<div class="drill-actions" style="margin-top: 18px;">
|
||||
<button class="drill-back" data-target="opportunities-root">← Opportunities</button>
|
||||
<a class="open-link" href="https://claude.ai/code/routines/trig_01XRb81y9pAWDube5kjRUvGT" target="_blank" rel="noopener">Open Conference Scout ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,350 @@
|
||||
/* ---------- Opportunities calendar ----------
|
||||
Each entry: { start: "YYYY-MM-DD", end: "YYYY-MM-DD" (defaults to start),
|
||||
title, location, attendee (optional) }.
|
||||
Multi-day entries render as one bar spanning their full date range;
|
||||
bars continuing past a week boundary lose the rounded corner on that side. */
|
||||
var OPPORTUNITIES = [
|
||||
{ start: "2026-08-23", end: "2026-08-27", title: "SPIE", location: "San Diego, CA", attendee: "Peter", url: "https://spie.org/conferences-and-exhibitions/optics-and-photonics",
|
||||
abstract: "SPIE's flagship annual optics and photonics conference and exhibition, held every August in San Diego since the 1960s. Brings together 10,000+ scientists, engineers, and industry professionals across optical engineering, nanoscience, quantum science, and organic photonics." },
|
||||
{ start: "2026-11-10", end: "2026-11-13", title: "SEMICON Europa", location: "Munich, Germany", url: "https://www.semiconeuropa.org/",
|
||||
abstract: "Semiconductor and electronics manufacturing trade show co-located with electronica in Munich. Covers fab management, advanced packaging, and smart manufacturing across the chip supply chain, drawing 8,000+ visitors from equipment vendors, fabs, and OSATs." }
|
||||
];
|
||||
var MONTH_NAMES = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
||||
var MONTH_ABBR = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||
var oppCursor = new Date();
|
||||
oppCursor.setDate(1);
|
||||
function oppKey(d) {
|
||||
return d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0");
|
||||
}
|
||||
function parseOppDate(s) {
|
||||
var parts = s.split("-").map(Number);
|
||||
return new Date(parts[0], parts[1] - 1, parts[2]);
|
||||
}
|
||||
function addOppDays(d, n) {
|
||||
var r = new Date(d);
|
||||
r.setDate(r.getDate() + n);
|
||||
return r;
|
||||
}
|
||||
function buildOppEvents() {
|
||||
return OPPORTUNITIES.map(function (o) {
|
||||
return {
|
||||
title: o.title, location: o.location, attendee: o.attendee, url: o.url, abstract: o.abstract,
|
||||
start: parseOppDate(o.start), end: parseOppDate(o.end || o.start)
|
||||
};
|
||||
}).sort(function (a, b) { return a.start - b.start; });
|
||||
}
|
||||
function formatOppDateRange(start, end) {
|
||||
if (start.getTime() === end.getTime()) {
|
||||
return MONTH_ABBR[start.getMonth()] + " " + start.getDate() + ", " + start.getFullYear();
|
||||
}
|
||||
if (start.getFullYear() === end.getFullYear()) {
|
||||
if (start.getMonth() === end.getMonth()) {
|
||||
return MONTH_ABBR[start.getMonth()] + " " + start.getDate() + "–" + end.getDate() + ", " + start.getFullYear();
|
||||
}
|
||||
return MONTH_ABBR[start.getMonth()] + " " + start.getDate() + " – " + MONTH_ABBR[end.getMonth()] + " " + end.getDate() + ", " + start.getFullYear();
|
||||
}
|
||||
return MONTH_ABBR[start.getMonth()] + " " + start.getDate() + ", " + start.getFullYear() + " – " + MONTH_ABBR[end.getMonth()] + " " + end.getDate() + ", " + end.getFullYear();
|
||||
}
|
||||
function renderOppEventList() {
|
||||
var listEl = document.getElementById("opp-event-list");
|
||||
if (!listEl) return;
|
||||
listEl.innerHTML = "";
|
||||
var events = buildOppEvents();
|
||||
if (!events.length) {
|
||||
var empty = document.createElement("div");
|
||||
empty.className = "opp-list-empty";
|
||||
empty.textContent = "No events yet.";
|
||||
listEl.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
events.forEach(function (e) {
|
||||
var row = document.createElement("div");
|
||||
row.className = "opp-list-row";
|
||||
|
||||
var meta = document.createElement("div");
|
||||
meta.className = "opp-list-meta";
|
||||
var datesEl = document.createElement("div");
|
||||
datesEl.className = "opp-list-dates";
|
||||
datesEl.textContent = formatOppDateRange(e.start, e.end);
|
||||
meta.appendChild(datesEl);
|
||||
if (e.location) {
|
||||
var locEl = document.createElement("div");
|
||||
locEl.className = "opp-list-loc";
|
||||
locEl.textContent = e.location;
|
||||
meta.appendChild(locEl);
|
||||
}
|
||||
if (e.attendee) {
|
||||
var attEl = document.createElement("div");
|
||||
attEl.className = "opp-list-attendee";
|
||||
attEl.textContent = "Attending: " + e.attendee;
|
||||
meta.appendChild(attEl);
|
||||
}
|
||||
if (e.url) {
|
||||
var linkEl = document.createElement("a");
|
||||
linkEl.className = "opp-list-link";
|
||||
linkEl.href = e.url;
|
||||
linkEl.target = "_blank";
|
||||
linkEl.rel = "noopener";
|
||||
linkEl.textContent = "Website";
|
||||
meta.appendChild(linkEl);
|
||||
}
|
||||
row.appendChild(meta);
|
||||
|
||||
var about = document.createElement("div");
|
||||
about.className = "opp-list-about";
|
||||
var badge = document.createElement(e.url ? "a" : "span");
|
||||
badge.className = "opp-list-badge";
|
||||
badge.textContent = e.title;
|
||||
if (e.url) {
|
||||
badge.href = e.url;
|
||||
badge.target = "_blank";
|
||||
badge.rel = "noopener";
|
||||
}
|
||||
about.appendChild(badge);
|
||||
if (e.abstract) {
|
||||
var abstractEl = document.createElement("p");
|
||||
abstractEl.className = "opp-list-abstract";
|
||||
abstractEl.textContent = e.abstract;
|
||||
about.appendChild(abstractEl);
|
||||
}
|
||||
row.appendChild(about);
|
||||
|
||||
row.addEventListener("click", function () {
|
||||
oppCursor = new Date(e.start.getFullYear(), e.start.getMonth(), 1);
|
||||
renderOppCalendar();
|
||||
});
|
||||
listEl.appendChild(row);
|
||||
});
|
||||
}
|
||||
function renderOppCalendar() {
|
||||
var label = document.getElementById("opp-month-label");
|
||||
var grid = document.getElementById("opp-cal-grid");
|
||||
if (!label || !grid) return;
|
||||
label.textContent = MONTH_NAMES[oppCursor.getMonth()] + " " + oppCursor.getFullYear();
|
||||
grid.innerHTML = "";
|
||||
|
||||
var weekdayRow = document.createElement("div");
|
||||
weekdayRow.className = "opp-cal-weekdays";
|
||||
["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].forEach(function (wd) {
|
||||
var wdEl = document.createElement("div");
|
||||
wdEl.className = "opp-cal-weekday";
|
||||
wdEl.textContent = wd;
|
||||
weekdayRow.appendChild(wdEl);
|
||||
});
|
||||
grid.appendChild(weekdayRow);
|
||||
|
||||
var year = oppCursor.getFullYear(), month = oppCursor.getMonth();
|
||||
var firstDow = new Date(year, month, 1).getDay();
|
||||
var daysInMonth = new Date(year, month + 1, 0).getDate();
|
||||
var totalCells = Math.ceil((firstDow + daysInMonth) / 7) * 7;
|
||||
var gridStart = new Date(year, month, 1 - firstDow);
|
||||
var todayKey = oppKey(new Date());
|
||||
|
||||
var events = buildOppEvents();
|
||||
|
||||
for (var w = 0; w < totalCells / 7; w++) {
|
||||
var weekStart = addOppDays(gridStart, w * 7);
|
||||
var weekEnd = addOppDays(weekStart, 6);
|
||||
var weekEl = document.createElement("div");
|
||||
weekEl.className = "opp-cal-week";
|
||||
|
||||
var weekEvents = events
|
||||
.filter(function (e) { return e.start <= weekEnd && e.end >= weekStart; })
|
||||
.sort(function (a, b) { return a.start - b.start; });
|
||||
var lanes = [];
|
||||
weekEvents.forEach(function (e) {
|
||||
var colStart = Math.max(0, Math.round((Math.max(e.start, weekStart) - weekStart) / 86400000));
|
||||
var colEnd = Math.min(6, Math.round((Math.min(e.end, weekEnd) - weekStart) / 86400000));
|
||||
var lane = 0;
|
||||
while (lanes[lane] && lanes[lane].some(function (r) { return !(colEnd < r[0] || colStart > r[1]); })) lane++;
|
||||
if (!lanes[lane]) lanes[lane] = [];
|
||||
lanes[lane].push([colStart, colEnd]);
|
||||
e._col = [colStart, colEnd];
|
||||
e._lane = lane;
|
||||
});
|
||||
var cellMinHeight = Math.max(96, 34 + lanes.length * 24 + 10);
|
||||
|
||||
for (var d = 0; d < 7; d++) {
|
||||
var cellDate = addOppDays(weekStart, d);
|
||||
var outside = cellDate.getMonth() !== month;
|
||||
var key = oppKey(cellDate);
|
||||
var cell = document.createElement("div");
|
||||
cell.className = "opp-cal-cell" + (outside ? " outside" : "") + (key === todayKey ? " today" : "");
|
||||
cell.style.minHeight = cellMinHeight + "px";
|
||||
var numEl = document.createElement("div");
|
||||
numEl.className = "opp-cal-daynum";
|
||||
numEl.textContent = cellDate.getDate();
|
||||
cell.appendChild(numEl);
|
||||
weekEl.appendChild(cell);
|
||||
}
|
||||
|
||||
weekEvents.forEach(function (e) {
|
||||
var bar = document.createElement("div");
|
||||
bar.className = "opp-cal-event-bar";
|
||||
if (e.start < weekStart) bar.classList.add("continues-left");
|
||||
if (e.end > weekEnd) bar.classList.add("continues-right");
|
||||
bar.style.left = "calc(" + (e._col[0] / 7 * 100) + "% + 4px)";
|
||||
bar.style.width = "calc(" + ((e._col[1] - e._col[0] + 1) / 7 * 100) + "% - 8px)";
|
||||
bar.style.top = (34 + e._lane * 24) + "px";
|
||||
var titleEl = document.createElement(e.url ? "a" : "span");
|
||||
titleEl.className = "opp-cal-event-title";
|
||||
titleEl.textContent = e.title;
|
||||
if (e.url) {
|
||||
titleEl.href = e.url;
|
||||
titleEl.target = "_blank";
|
||||
titleEl.rel = "noopener";
|
||||
}
|
||||
bar.appendChild(titleEl);
|
||||
if (e.location) {
|
||||
var locEl = document.createElement("span");
|
||||
locEl.className = "opp-cal-event-loc";
|
||||
locEl.textContent = e.location;
|
||||
bar.appendChild(locEl);
|
||||
}
|
||||
bar.title = e.title + (e.location ? " — " + e.location : "") + (e.attendee ? " (" + e.attendee + ")" : "");
|
||||
weekEl.appendChild(bar);
|
||||
});
|
||||
|
||||
grid.appendChild(weekEl);
|
||||
}
|
||||
}
|
||||
/* ---------- Conference Scout results ----------
|
||||
Snapshot of the weekly research agent's report, hand-reviewed and pasted in here.
|
||||
Each entry: { title, dates (display text), location, url, reason }. */
|
||||
var SCOUT_RESULTS = [
|
||||
{ title: "PCIM Asia 2026", dates: "Aug 26–28, 2026", location: "Shenzhen, China",
|
||||
reason: "Asia's leading power electronics / wide-bandgap semiconductor trade show — direct GaN/SiC device and materials audience. Existing focus area." },
|
||||
{ title: "IMAPS Onshoring Workshop 2026", dates: "Aug 31–Sept 2, 2026", location: "Arlington, VA, USA",
|
||||
url: "https://imaps.org/page/Onshoring",
|
||||
reason: "Workshop on the US Defense Industrial Base's microelectronics packaging/assembly onshoring — a direct entry point for X-ray/semiconductor characterization into defense supply chains. Potential new business (defense)." },
|
||||
{ title: "SEMICON Taiwan 2026", dates: "Sept 2–4, 2026", location: "Taipei, Taiwan",
|
||||
url: "https://www.semicontaiwan.org/",
|
||||
reason: "Major semiconductor equipment/materials/metrology trade show in a top compound-semiconductor manufacturing hub. Existing focus area." },
|
||||
{ title: "WiPDA-Europe 2026", dates: "Sept 20–22, 2026", location: "Aalborg, Denmark",
|
||||
url: "https://www.energy.aau.dk/wipda-europe-2026-e156452",
|
||||
reason: "IEEE workshop on wide-bandgap (GaN/SiC) power devices — niche but highly targeted technical audience. Existing focus area." },
|
||||
{ title: "XTOP 2026", dates: "Sept 21–25, 2026", location: "Karlsruhe, Germany",
|
||||
url: "https://xtop2026.kit.edu",
|
||||
reason: "16th Biennial Conference on High-Resolution X-Ray Diffraction and Imaging — direct X-ray diffraction/imaging overlap. Existing focus area." },
|
||||
{ title: "NOBUGS 2026", dates: "Sept 21–25, 2026 (core Sept 22–24)", location: "Schenefeld Campus (European XFEL), near Hamburg, Germany",
|
||||
url: "https://nobugs2026.xfel.eu/",
|
||||
reason: "Explicitly covers metadata/data formats and FAIR data management for beamline software; co-hosted by DESY/European XFEL on TXproducts' home campus — the single best topical fit found. Existing focus area." },
|
||||
{ title: "ICSCRM 2026", dates: "Sept 27–Oct 2, 2026", location: "Yokohama, Japan",
|
||||
reason: "Premier international conference on silicon carbide and related materials — direct match for the SiC/X-ray metrology work. Existing focus area." },
|
||||
{ title: "RDA 27th Plenary Meeting (P27)", dates: "Oct 6–8, 2026", location: "London, UK",
|
||||
url: "https://www.rd-alliance.org/plenaries/p27/",
|
||||
reason: "Research Data Alliance plenary — strong FAIR/research-data-infrastructure networking, though less domain-specific than NOBUGS. Existing focus area." },
|
||||
{ title: "AFLS2026", dates: "Nov 16–20, 2026", location: "Harare, Zimbabwe",
|
||||
url: "https://events.saip.org.za/event/255",
|
||||
reason: "African Light Source / African Physical Society joint meeting — the Africa-based synchrotron-development community, with materials/X-ray characterization topics. Existing focus area." },
|
||||
{ title: "MRS Fall Meeting & Exhibit 2026", dates: "Nov 29–Dec 4, 2026", location: "Boston, MA, USA",
|
||||
url: "https://www.mrs.org/",
|
||||
reason: "Broad materials-science conference with strong X-ray characterization tracks. Existing focus area." },
|
||||
{ title: "GOMACTech 2027", dates: "March 22–25, 2027", location: "Pasadena, CA, USA",
|
||||
url: "https://www.gomactech.net/",
|
||||
reason: "The premier US DoD microelectronics conference — covers GaN/SiC power devices and X-ray characterization/reliability for defense microelectronics. Potential new business (defense)." },
|
||||
{ title: "IEEE RadarConf'27", dates: "May 1–6, 2027", location: "Bangalore, India",
|
||||
url: "https://radarconf2027.org/",
|
||||
reason: "Radar hardware relies heavily on GaN/SiC RF power amplifiers — a defense-radar audience in a growing Indian defense-electronics market. Potential new business (defense)." }
|
||||
];
|
||||
function renderOppScoutList() {
|
||||
var listEl = document.getElementById("opp-scout-list");
|
||||
if (!listEl) return;
|
||||
listEl.innerHTML = "";
|
||||
if (!SCOUT_RESULTS.length) {
|
||||
var empty = document.createElement("div");
|
||||
empty.className = "opp-list-empty";
|
||||
empty.textContent = "No results yet.";
|
||||
listEl.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
SCOUT_RESULTS.forEach(function (s) {
|
||||
var row = document.createElement("div");
|
||||
row.className = "opp-list-row";
|
||||
row.style.cursor = "default";
|
||||
|
||||
var meta = document.createElement("div");
|
||||
meta.className = "opp-list-meta";
|
||||
var datesEl = document.createElement("div");
|
||||
datesEl.className = "opp-list-dates";
|
||||
datesEl.textContent = s.dates;
|
||||
meta.appendChild(datesEl);
|
||||
if (s.location) {
|
||||
var locEl = document.createElement("div");
|
||||
locEl.className = "opp-list-loc";
|
||||
locEl.textContent = s.location;
|
||||
meta.appendChild(locEl);
|
||||
}
|
||||
if (s.url) {
|
||||
var linkEl = document.createElement("a");
|
||||
linkEl.className = "opp-list-link";
|
||||
linkEl.href = s.url;
|
||||
linkEl.target = "_blank";
|
||||
linkEl.rel = "noopener";
|
||||
linkEl.textContent = "Website";
|
||||
meta.appendChild(linkEl);
|
||||
}
|
||||
row.appendChild(meta);
|
||||
|
||||
var about = document.createElement("div");
|
||||
about.className = "opp-list-about";
|
||||
var badge = document.createElement(s.url ? "a" : "span");
|
||||
badge.className = "opp-list-badge";
|
||||
badge.textContent = s.title;
|
||||
if (s.url) {
|
||||
badge.href = s.url;
|
||||
badge.target = "_blank";
|
||||
badge.rel = "noopener";
|
||||
}
|
||||
about.appendChild(badge);
|
||||
if (s.reason) {
|
||||
var reasonEl = document.createElement("p");
|
||||
reasonEl.className = "opp-list-abstract";
|
||||
reasonEl.textContent = s.reason;
|
||||
about.appendChild(reasonEl);
|
||||
}
|
||||
row.appendChild(about);
|
||||
|
||||
listEl.appendChild(row);
|
||||
});
|
||||
}
|
||||
document.getElementById("opp-prev-btn").addEventListener("click", function () {
|
||||
oppCursor.setMonth(oppCursor.getMonth() - 1);
|
||||
renderOppCalendar();
|
||||
});
|
||||
document.getElementById("opp-next-btn").addEventListener("click", function () {
|
||||
oppCursor.setMonth(oppCursor.getMonth() + 1);
|
||||
renderOppCalendar();
|
||||
});
|
||||
document.getElementById("opp-today-btn").addEventListener("click", function () {
|
||||
oppCursor = new Date();
|
||||
oppCursor.setDate(1);
|
||||
renderOppCalendar();
|
||||
});
|
||||
renderOppEventList();
|
||||
renderOppCalendar();
|
||||
renderOppScoutList();
|
||||
|
||||
/* ---------- Module self-registration ----------
|
||||
Everything below wires this module into the shared shell (app.js):
|
||||
the drill-view registry, the hero-nav button, and the tab's default view.
|
||||
This runs once, right after opportunities.html is injected into the DOM. */
|
||||
HERO_NAV_BY_TAB.opportunities = ["hero-nav-scout"];
|
||||
DRILL_DEFAULT_VIEW.opportunities = "opportunities-root";
|
||||
DRILL_CONFIG.opportunities = {
|
||||
crumbsEl: "opp-crumbs",
|
||||
crumbs: {
|
||||
"opportunities-root": [["Opportunities", "opportunities-root"]],
|
||||
"scout": [["Opportunities", "opportunities-root"], ["Conference Scout", "scout"]]
|
||||
}
|
||||
};
|
||||
document.querySelectorAll("#tab-opportunities .drill-back, #tab-opportunities .drill-next").forEach(function (b) {
|
||||
b.addEventListener("click", function () { setDrillView("opportunities", b.getAttribute("data-target")); });
|
||||
});
|
||||
setDrillView("opportunities", DRILL_DEFAULT_VIEW.opportunities);
|
||||
document.getElementById("hero-nav-scout-btn").addEventListener("click", function () {
|
||||
setDrillView("opportunities", "scout");
|
||||
});
|
||||
if (document.querySelector(".sidebar-nav-btn.active[data-tab=\"opportunities\"]")) {
|
||||
document.getElementById("hero-nav-scout").style.display = "flex";
|
||||
}
|
||||
Reference in new issue
Block a user